可能不需要 redux 了, 但不该用 hook 去取代它
前言
最近再补有关 react hook 的文档,在看完基础 hook 的章节时 (useState, useEffect, useContext),可以大致明白 hook 的用途了,简单的来说,hook 可以让函数组件也能拥有自己的 state
,并且可以使用 componentDidMount
, componentDidUpdate
, shouldComponentUpdate
等 class 组件里面才有的特性,后面看到额外 hook 的章节时,里面有一个名叫 useReducer
的 api,这让我对 hook 的用途又打上了一个新的问好,看完了 useReducer 的相关介绍以及用法后,就在想,这个叫 useReducer 的 api 会不会是用来取代 redux
的呢?于是乎就在网上查阅了一下相关资料,最后从网上得出的结论就是,hook 是可以用来取代 redux 的,但是不能用 hook 去 完全取代
redux, 因为他们两者的出发点不同,解决问题的场景也有所不同。为了验证 hook 到底能不能完全取代 redux,我也做了相关的尝试,最终得出结论,如果想使用 hook 来完全取代 redux,还是有一些特定的需求是无法满足的,在这里记录一下。
如何使用 hook 取代 redux
思路:
- 使用 useReducer 维护 state,这一步用于取代 redux
- 使用 createContext 让 state 进行提升,这一步用于取代 react-redux
具体步骤:
创建名叫 toDoStore.ts
的文件,从文件名大概可以知道,这个文件是用来管理 state 的。
import React, { useReducer, useContext } from 'react';
// state
interface ToDoState {
count: number;
list: Array<String>;
}
// action type
enum ToDoActionType {
UPDATE_COUNT = 'updateCount',
UPDATE_LIST = 'updateList',
}
// action
type ToDoAction =
| {
type: ToDoActionType.UPDATE_COUNT;
count: number;
}
| {
type: ToDoActionType.UPDATE_LIST;
list: Array<String>;
};
// reducer
const reducer = (state: ToDoState, action: ToDoAction): ToDoState => {
switch (action.type) {
case ToDoActionType.UPDATE_COUNT:
return {
...state,
count: action.count,
};
case ToDoActionType.UPDATE_LIST:
return {
...state,
list: action.list,
};
default:
return state;
}
};
// 初始化 state
const initToDoState: ToDoState = {
count: 0,
list: [],
};
// 初始化 context
const ToDoStoreContext = React.createContext<
| {
state: ToDoState;
dispatch: React.Dispatch<ToDoAction>;
}
| undefined
>(undefined);
// contexct provider
const ToDoStoreContextProvider = ({
children,
}: {
children: React.ReactElement;
}): React.ReactElement => {
const [state, dispatch] = useReducer(reducer, initToDoState);
// 我们将 useReducer 这个钩子,绑定在 context 的 provider 中
return <ToDoStoreContext.Provider value={{ state, dispatch }}>{children}</ToDoStoreContext.Provider>;
};
// use context
const useToDoContext = (): {
state: ToDoState;
dispatch: React.Dispatch<ToDoAction>;
} => {
const toDoStoreContext = useContext(ToDoStoreContext);
if (toDoStoreContext === undefined) {
// 组件想使用 toDoContext 必须嵌套在 ToDoStoreContext.Provider 中(HOC)
throw new Error('useToDoContext 必须在 ToDoStoreContext.Provider 中使用');
}
return toDoStoreContext;
};
export { ToDoStoreContextProvider, useToDoContext, ToDoActionType };
创建一个或者多个组件,用于获取的 state,以及调用 dispatch 来改变的 state
import React from 'react';
import { useToDoContext, ToDoActionType } from './toDoStore';
const ShowToDoState: React.FunctionComponent = () => {
const { state } = useToDoContext();
return (
<section>
<span>{state.count}</span>
<ul>
{state.list.map((listItem, index) => (
<li key={index}>{listItem}</li>
))}
</ul>
</section>
);
};
const EditToDoState: React.FunctionComponent = () => {
const { state, dispatch } = useToDoContext();
return (
<section>
<button
type="button"
onClick={() => dispatch({ type: ToDoActionType.UPDATE_COUNT, count: state.count + 1 })}
>
increment count
</button>
<button
type="button"
onClick={() =>
dispatch({ type: ToDoActionType.UPDATE_LIST, list: [...state.list, 'hello world'] })
}
>
add item
</button>
</section>
);
};
const App: React.FunctionComponent = () => (
<>
<ShowToDoState />
<EditToDoState />
</>
);
export default App;
将最顶部组件用 ToDoStoreContextProvider 进行包裹,这样子组件才能进行订阅 context 的变化
import React from 'react';
import ReactDOM from 'react-dom';
import { ToDoStoreContextProvider } from './toDoStore';
import App from './App';
ReactDOM.render(
<ToDoStoreContextProvider>
<App />
</ToDoStoreContextProvider>,
document.getElementById('root')
);
demo:
总结
从上面使用 hook 取代 redux 的过程中,会发现有以下几点是 hook 无法满足的:
- useReducer 无法进行 combine,这意味着你将不能对 state 进行分组管理,如果你强行对 state 进行分组,那么你将创建多个 context,这无异于提高了整个应用的复杂度
- 并没有提供 react-redux 那样的 HOC(mapStateToProps mapDispatchToProps),这意味着你不能快速的查看组件到底使用了那些 state,需要 ctrl + f 来进行确认
- useContext 所带来的性能问题,一旦触发 dispatch,所有使用了 context 的组件都会进行重新的 render,虽然可以手动处理,但只要稍有不慎,可以说是前功尽弃
可能说的很不严谨, 经代表个人观点!