Redux简介

Redux是一个集中存储和管理全局状态的库,通过reducer纯函数来定义怎样更新state,然后通过dispatch action更新state,state每次更新都要返回一个新对象,也就是说state是不可变的。

基于reducer纯函数和不可变state,redux应用可以实现时间旅行式的调试;

特点:

  • 单一数据源store;
  • 纯函数reducer;
  • 不可变state;

标准(传统、Legacy)Redux

1
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 1.1 定义action
enum ActionType {
INCREASE,
DECREASE
}
const increase = () => {
return {
type: ActionType.INCREASE,
}
}
const decrease = () => {
return {
type: ActionType.DECREASE,
}
}

// 1.2 定义reducers
const reducer1 = (state, action) => {
if (action.type === ActionType.INCREASE) {
return { ...state, count: state.count + 1 };
}
if (action.type === ActionType.DECREASE) {
return { ...state, count: state.count - 1 };
}
return state;
}
const reducer2 = (state, action) => {
//....
}
const reducers = combineReducer({ counter: reducer1, others: reducer2 });

// 2. 创建store
const initialState = {
counter: {
count: 0
},
others: {
// ...
}
};
const enhancer = compose(
applyMiddleware(thunk, logger),
DevTools.instrument()
);
const store = createStore(reducers, initialState, enhancer);

// React组件
function ReactComponent () {
const [num, setNum] = React.useState(state.getState().counter.count);

const handleClickEvent = () => {
// 4. dispatch action
store.dispatch(increase());
}

React.useEffect(() => {
// 3. 订阅更新
const unsubscribe = store.subscribe(() => {
// 5. 更新React state
setNum(store.getState().count);
})
// 6. 组件卸载时,解除订阅
return () => {
unsubscribe();
}
}, [store]);

return <div>
<p>{ num }</p>
<button onClick={handleClickEvent}>+ 1</button>
</div>;
}

异步Action解决方案:redux-thunk,redux-promise

1
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
// redux-thunk
const thunkActionCreator = () => {
return function thunkAction (dispatch, getState) {
setTimeout(() => {
dispatch(someAction);
}, 1000);
}
}

dispatch(thunkActionCreator());
// dispatch会调用redux-thunk注入的逻辑:执行thunk函数,传入dispatch和getState,返回执行结果
// 所以dispatch(thunk)的执行结果就是thunk()的执行结果
// 进一步地,thunk如果返回一个Promise(或任何其他值),就可以链式调用Promise的一系列方法,或者通过async function编写流程化的action

// 例如
const fetchBlogs = (uid) => {
return async (dispatch, getState) => {
const userInfo = await fetchUserInfo(uid);
// 用户详细数据还未加载,则加载进redux store
if (!getState().userInfo) {
dispatch({ type: "USER_INFO", payload: userInfo });
}
const blogs = await fetchBlogsByUserInfo(userInfo);
dispatch({ type: "BLOGS_LISt", payload: blogs });
}
}
1
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
// redux-promise

// FSA,Flux Standard Action,只包含以下属性: type (required), payload, error, meta

// 1. 第一种写法,Promise
// redux-promise通过then取得resolved value of Promise,然后dispatch resolved value of Promise
const asyncActionCreator1 = () => new Promise((resolve) => {
setTimeout(() => {
resolve({
type: "PROMISE",
value: 11111
});
}, 1000);
})
// 2. 第二种,FSA
// 外层payload初始化时就会执行executor,然后redux-promise通过promise.then取得resolved value of Promise,然后dispatch一个对象{...action, payload: resolved value of Promise}
const asyncActionCreator2 = () => ({
type: "FSA",
payload: new Promise((resolve) => {
setTimeout(() => {
resolve(22222);
}, 1000);
})
})
// reducer略有不同
const reducer = (state, action) => {
switch (action.type) {
case "PROMISE": return { ...state, value: action.value };
case "FSA": return { ...state, value: action.payload };
default: return state;
}
}

dispatch(asyncActionCreator1());
dispatch(asyncActionCreator2());

简化react组件订阅redux store的过程 —— react-redux

Hooks:

  • useSelector,构建redux state选择器,并订阅redux state的更新
  • useDispatch,获取dispatch函数,用于分发action
  • useStore,获取redux store实例

Components:

  • Provider,其实就是react内置组件Context.Provider
  • connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?),是一个HOC组件,用于向react组件内注入redux state和dispatch函数,使组件订阅redux state的更新
    • mapStateToProps(state, ownProps?),用于将redux state注入组件的props中,并使组件订阅redux state的更新,可以访问到ownProps(具体含义见下文细节
    • mapDispatchToProps
      • mapDispatchToProps(dispatch, ownProps?)用于将dispatch封装为各种action的派发函数,并注入connected component,同样可以访问到ownProps
      • mapDispatchToPropsObj,作用相同,但是一个action creators构成的对象,这个对象会传递给bindActionCreators,将结果注入connected component,该形式更为简便,redux推荐使用该形式注入action派发函数
    • mergeProps(stateProps,dispatchProps,ownProps)用于合并stateProps、dispatchProps和ownProps,默认返回{ ...ownProps, ...stateProps, ...dispatchProps }
    • options
      • context,只有v6可用,用户可提供自定义的context
      • areStatesEqual,默认为严格比较(===),决定是否重新执行mapStateToProps;
      • areOwnPropsEqual,默认为浅比较,决定ownProps前后是否一致
      • areStatePropsEqual,默认为浅比较,决定stateProps前后是否一致
      • areMergedPropsEqual,默认为浅比较,决定mergeProps前后是否一致
      • forwardRef,只有v6可用,设为true,则connect执行后得到的HOC将ref转发到connected components

Utils:

  • bindActionCreators(),第一个参数为action creator或action creators构成的对象,第二个参数为dispatch,对于单个action creator会返回一个绑定dispatch后的匿名函数,对于action creators对象会返回一个具有同样key的对象,其value是绑定dispatch后的函数;
  • batch(),本质是react的unstable_batchedUpdates,将多次dispatch合并进一次组件渲染中,react18默认启用batched updates特性,所以不必使用该api;

性能优化:

  • 通过selector缓存数据提取函数;
  • mapStateToProps应当是纯函数,应该是同步执行的,尽量避免耗时操作或只在输入改变时执行耗时操作,只在必要时返回一个新的引用;

细节

  • ownProps即传递给connected component的props;
  • 对于mapStateToProps,state或ownProps发生改变会导致mapStateToProps的重新调用;
  • connect实现了一个浅比较的shouldComponentUpdate,当stateProps或ownProps的引用改变或字段改变,会触发重渲染;
  • 若mapStateToProps未定义,则connected component不会订阅redux state的更新;
  • 若mapDispatchToProps未定义,connected component的props会接收到dispatch;若定义了,则不会传入dispatch;

重点

现代化的react-redux不推荐通过connect集成react与redux store,取而代之的是直接在函数组件中使用useSelector、useDispatch

原理:

  1. 数据传递

Provider在顶层提供一个Context.Provider,并将store以context value形式提供给下层组件,connect从context中取到store,并对mapStateToProps、mapDispatchToProps、mergeProps进行一系列计算,将计算得到的props注入被connect的组件

  1. 组件更新

7.0版本是通过connect执行后得到HOC,HOC内订阅store更新,并通过useReducer更新子组件;
8.0版本更新组件的方式改为使用useSyncExternalStore,通过redux store的subscribe订阅store的更新,dispatch action之后store发生变化,执行react通过subscribe注册进来的listener,然后react触发一次同步更新(其实之后在render和commit阶段还会进行store一致性检查,一旦此时store改变,就再触发一次同步更新)

现代Redux —— Redux Toolkit(RTK)

API:

  • configureStore,创建、初始化store,内置了redux-thunk等middleware
  • createSlice,定义局部的reducer,自动生成action creator
  • createAsyncThunk,创建thunk action creator
1
2
3
4
// store.js
const store = configureStore({
user: userSlice.reducer
})
1
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
// userSlice.js
const name = "user"
const userSlice = createSlice({
name,
initialState: {
name: "暂未登录"
uid: undefined,
status: "idle"
},
reducers: {
resetUserInfo: (state, action) => {
// ...
}
}
// 一般处理thunk action的回调dispatch的action
extraReducers: (builder) => {
builder
.addCase(login.pending, (state) => {
state.status = "pending";
})
.addCase(login.fulfilled, (state, action) => {
state.status = "fulfilled";
state = {...state, ...action.payload};
})
.addCase(login.rejected, (state, action) => {
state.status = "rejected";
state.error = action.payload;
})
}
})

const login = createAsyncThunk(`${name}/login`, async (args, thunkAPI) => {
const {uname, pwd} = args;
const user = await loginApi(uname, pwd);
return user;
})
1
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
// Login.jsx
function Login() {
const dispatch = useDispatch();
const isLoading = useSelector((state) => state.status === "pending");

const [name, setName] = useState("");
const [pwd, setPwd] = useState("");

return <div>
<div>
<label>Name</label>
<input value={name} onChange={(e) => {
setName(e.currentTarget.value);
}}>
</div>
<div>
<label>Password</label>
<input value={pwd} onChange={(e) => {
setPwd(e.currentTarget.value);
}}>
</div>
<button disabled={isLoading} onClick={() => {
dispatch(login(name, pwd))
}}> Login </button>
</div>
}
1
2
3
4
5
6
7
8
9
// UserInfo.jsx
const UserInfo = () => {
const {name, pwd} = useSelector((state) => state.user);

return <div>
<p>Name: {name}</p>
<p>Password: {pwd}</p>
</div>
}
1
2
3
4
5
6
7
8
// App.jsx
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<Login />
<UserInfo />
</Provider>
)