Redux 快速入门
# redux
# redux 的基本介绍
作为前端开发者,一般都听过 redux 的大名。这个东西就是做组件状态管理的。
redux 并不是 react 官方推出的,是一个个人作者 Dan Abramov 推出。
# redux 和 react 的关系?
一般开发react,要对组件的状态进行管理的话,一般都使用 redux。
redux 实际上可以和其他框架一起使用,比如你可以用到 vue,angular,jquery。
就好比 javascript 和 java 的关系(没有关系)
# redux 的设计哲学
# single source of truth
翻译成中中文就是“单一真相”,在redux中,有一个管理组件状态的仓库,所有组件的状态都从仓库来获取。如果组件的状态发生改变,这个改变会被提交给仓库。
在 redux中,由于遵循单一真相的原则,所以所有的组件都由仓库来管理
# State is read-only
翻译成中文就是状态只读。在redux,我们确实无法直接修改仓库里面的状态。
在redux,虽然不能直接去修改仓库的状态,可以生成一份新的状态树,用新的状态树去覆盖旧的状态树。
# changes are made with pure functions called reducer
翻译成中文“要改变状态使用纯函数,这样的纯函数被称之为reducer”。
接下来我们来看一下 redux 的一个基本使用。
我们还是以计数器来介绍redux的代码,首先我们用原来的react 的方法来书写一个计数器
import React,{useState,useRef} from 'react'
export default function App(){
let [count,setCount] = useState(0);
const selEle = useRef();
const increment = ()=>{
const num = selEle.current.value * 1;
setCount(count+num);
}
const decrement = ()=>{
const num = selEle.current.value * 1;
setCount(count-num);
}
const incrementIfOdd = ()=>{
if(count % 2 === 1){
const num = selEle.current.value * 1;
setCount(count+num);
}
}
const incrementAsync = ()=>{
setTimeout(()=>{
const num = selEle.current.value * 1;
setCount(count+num);
},1000)
}
return (
<div>
<p>你点击了{count}次!</p>
<select name="" id="" ref={selEle}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={incrementIfOdd}>奇数增加</button>
<button onClick={incrementAsync}>异步增加</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
在上面的代码中,我们组件的状态都在App这个组件里面,如果存在其他的组件,那么这个其他的组件,也会有一份自己的状态。
接下来,我们用 redux 来改写上面的这个计数器,要创建一个仓库来保存状态:
- actions:负责描述如何生成新的状态树
- actionType:在派发action到reducer 的时候,会有一个类型。
- reducers:负责生成新的状态树,这个状态树会被dispatch 到仓库里面。
- store:仓库,存储组件的状态
// actionType.js
// action 的 type 值一般会单独拿一个文件来写
// 目的是为了方便维护
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
// actions.js
// 描述我要如何修改状态
import {INCREMENT,DECREMENT} from './actionType'
// 工厂函数:专门负责生产 action 对象
export const increment = number => ({
type : INCREMENT,
number
})
export const decrement = number => ({
type : DECREMENT,
number
})
// reducers.js
import {INCREMENT,DECREMENT} from './actionType'
// 是一个纯函数,里面确定了如何改变状态
// 注意,在reducer中,不是直接修改仓库状态
// 而是 reducer 会返回一个新的状态树,新的状态树去覆盖旧的状态
// 该纯函数有两个参数,一个是初始状态,一个是派发过来action
// 派发过来的 action,是对如何修改仓库状态做的一个描述
// 它是一个对象,一般有两个属性,type(增还是减),number,增加多少,减少多少
// action 是一个对象 {type : 'increment',number : 2}
export default function counter(state=0,action){
switch(action.type){
case INCREMENT:
return state + action.number;
case DECREMENT:
return state - action.number;
default:
return state;
}
}
// store.js
// 这是仓库文件
import {createStore} from 'redux' // 创建仓库的方法,来源于 redux
import counter from './reducers' // 引入 reducers 里面的 reducer
//在使用 createStore 创建仓库的时候,是需要传递参数的
// 传递一个 reducer 进去
export default createStore(counter)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
最后,每一次的仓库的状态的改变,我们需要重新渲染视图,所以需要调用 store 的 subscribe 方法,每次状态更新,都会自动调用这个方法,在这个方法中,我们重新渲染视图。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from 'react-router-dom'
import store from './redux/store'; // 引入仓库了
const render = ()=>{
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App store={store}/>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
}
render();
// 监听方法,一旦仓库的状态发生改变,就会触发这个方法
// 那么我们就应该更新我们的视图
store.subscribe(render);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# redux devtools
首先,在 google商店下载 redux devtools,安装好之后,还无法直接使用该工具,会提示找不到仓库。
所以,我们需要添加下面的代码:
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
// store.js
// 这是仓库文件
import {createStore} from 'redux' // 创建仓库的方法,来源于 redux
import counter from './reducers' // 引入 reducers 里面的 reducer
//在使用 createStore 创建仓库的时候,是需要传递参数的
// 传递一个 reducer 进去
export default createStore(
counter,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
2
3
4
5
6
7
8
9
10
11
# 使用中间件
redux 的核心概念其实很简单,将状态数据保存在state里面,发起一个action,action会到reducer,reducer进行计算出新的state,去覆盖旧的state。
当我们dispatch一个action的时候,这个action就直接到达reducer。
我们可以在action到达reducer之前,进行简单的处理,所以这个时候就需要中间件了。
这边主要介绍一下中间件的使用:
- redux-logger
- redux-thunk
要使用中间件,首先第一步还是安装:
npm i redux-logger
接下来修改仓库里面的代码:
// 这是仓库文件
import {createStore,applyMiddleware} from 'redux' // 创建仓库的方法,来源于 redux
import logger from 'redux-logger'
import counter from './reducers' // 引入 reducers 里面的 reducer
//在使用 createStore 创建仓库的时候,是需要传递参数的
// 传递一个 reducer 进去
export default createStore(
counter,
applyMiddleware(logger)
)
2
3
4
5
6
7
8
9
10
中间件是可以一次性应用多个,如果涉及到多个中间件,一定要注意顺序。
# 异步中间件
首先第一步,还是安装中间件
npm i redux-thunk
在 store里面引入这个中间件
// 这是仓库文件
import {createStore,applyMiddleware} from 'redux' // 创建仓库的方法,来源于 redux
import thunk from 'redux-thunk'
import counter from './reducers' // 引入 reducers 里面的 reducer
//在使用 createStore 创建仓库的时候,是需要传递参数的
// 传递一个 reducer 进去
export default createStore(
counter,
applyMiddleware(thunk)
)
2
3
4
5
6
7
8
9
10
接下来,在组件中,我们就不再是派发一个actions对象了,
这个时候,我们dispatch 一个函数,如下:
export const asyncAdd = (number)=>{
// dispatch 等同于 store.dispatch
// getState 等同于 store.getState
return (dispatch,getState)=>{
// 在这个函数里面,我就可以做异步的操作
setTimeout(()=>{
//异步操作结束后,在重新派发action 到reducer
let action = increment(number);
dispatch(action)
},1000)
}
}
2
3
4
5
6
7
8
9
10
11
12
这个函数不会直接到达reducer,所以我们可以在这个函数中,做异步的操作,接下来再派发action到reducer。
# react-redux
因为 redux 和 react 的结合非常的好,所以,react 官方也基于 redux,提供了一个官方库,叫做react-redux,简化了一些redux 中的操作。但是核心概念是不变的。
详情就参见代码,代码中有详细的注解。
# useReducer
这是 react 中提供的默认hook中的一种,代码示例如下:
import React,{useRef,useReducer} from 'react'
import * as actions from './redux/actions'
import counter from './redux/reducers'
import { INCREMENT,DECREMENT } from './redux/actionType'
export default function App(props){
const selEle = useRef();
// useReducer 接收两个参数,第一个是reducer,初始状态值
const [state,dispatch] = useReducer(counter,0)
// 顺便一提,之前在介绍 hook 的时候,useState
// useState 背后就是用的 useReducer
const increment = ()=>{
const num = selEle.current.value * 1;
dispatch({type : INCREMENT,number : num})
}
const decrement = ()=>{
const num = selEle.current.value * 1;
dispatch({type : DECREMENT,number : num})
}
const incrementIfOdd = ()=>{
if(state % 2 === 1){
const num = selEle.current.value * 1;
dispatch({type : INCREMENT,number : num})
}
}
const incrementAsync = ()=>{
setTimeout(()=>{
const num = selEle.current.value * 1;
dispatch({type : INCREMENT,number : num})
},1000)
}
return (
<div>
<p>你点击了{state}次!</p>
<select name="" id="" ref={selEle}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={incrementIfOdd}>奇数增加</button>
<button onClick={incrementAsync}>异步增加</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53