useContext
可以考虑这样一个场景,如组件树的机构如下图所示,在App
组件中我们实现了用户登录,而登录状态需要组件都需使用,如A、B、C
等组件需要拿到登录态从而响应用户的操作。如果使用props
进行登录态的透传,会不可避免的进行一层一层的传递,如果嵌套层级特别深的时候,这种传递是很危险且不可控的。
useContext Api
可以在这种场景展现其的莫大作用,如下我们通过context
将登录态的内容进行传递。
// login.context.ts
import React, { useContext, useEffect } from 'react';
interface IUser {
name: string;
age: number;
id: string;
}
const LoginContext = React.createContext<IUser>();
export const LoginContextProvider = (props) => {
const [user, setUser] = useState<IUser>({});
useEffect(() => {
(async () => {
const userinfo = await fetch('user/get');
setUser(userinfo);
})();
}, []);
return (
<LoginContext.Provider value={user}>
{props.childred}
</LoginContext.Provider>
);
};
export useLoginContext = () => {
return useContext(LoginContext);
};
// App 使用
import { useLoginContext } from './login.context';
const App = () => {
const {name} = useLoginContext();
return (
<div>
当前登录用户是:{name}
</div>
);
};
const AppWithContext = () => {
return (
<LoginContextProvider>
<App />
</LoginContextProvider>
);
};
export default AppWithContext;
如上即可实现在任何需要的节点使用useLoginContext
获取到最新的user
信息,从而避免props
的透传,当context
的值更新时组件也会重新渲染。
useReducer
useReducer
作为useState
的替代方案,详细的文档可以参看官网文档https://zh-hans.reactjs.org/docs/hooks-reference.html#usereducer
。下面我们使用useReducer
将create-react-app
的示例重新实现一下。
// App.tsx
import React, { useReducer, useCallback } from 'react';
export default const App = () => {
const reducer = useCallback((preState, action) => {
const { type } = action;
switch(type) {
default:
return preState;
case 'increment':
return { count: preState.count + 1 };
}
}, []);
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>
<button
type="button"
onClick={() => dispatch({ type: 'increment' })}
>
count is: {state.count}
</button>
</p>
</div>
);
};
useContext + useReducer = Redux
在实际的React
开发项目的时候,有些全局状态数据会选择使用redux
进行管理,但随着React Hooks
的使用,有部分场景也可以使用useContext + useReducer
实现Redux
的功能。如下的场景我们就可以考虑使用useContext + useReducer
的方式实现数据共享。
- 部分复杂组件需要进行深层
props
数据传递 - 项目较小,但仍然需要共享全局状态数据
useContext + useReducer
两者结合替代Redux
思路还是很简单的:
userReducer
获取state
和dispatch
userContext
将state
和dispatch
共享到子组件
// Count.context.tsx
import React, { createContext, useCallback, Dispatch, useReducer, useContext } from 'react'
interface IState {
count: number;
}
interface IContext {
state: IState;
dispatch: Dispatch<{
type: string;
payload?: Partial<IState>;
}>;
}
const initValue: IState = {
count: 0,
};
const Context = createContext<IContext>({
state: initValue,
dispatch: () => {},
});
export const ReducerContextProvider: React.FC = (props) => {
const reducer = useCallback((preState: IState, action: {
type: string;
payload?: Partial<IState>;
}) => {
const { type, payload } = action;
switch(type) {
default:
return preState;
case 'increment':
return {
...preState,
count: preState.count + 1,
};
case 'decrement':
return {
...preState,
count: preState.count - 1,
};
case 'reset':
return {
...preState,
...payload,
};
}
}, []);
const [state, dispatch] = useReducer(reducer, initValue);
return (
<Context.Provider value={{state, dispatch}}>
{props.children}
</Context.Provider>
);
};
export const useReducerContext = () => {
return useContext(Context);
};
// App.tsx
import { ReducerContextProvider, useReducerContext } from './Count.context';
const App = () => {
const { state, dispatch } = useReducerContext();
return (
<div>
<p>
<button
type="button"
onClick={() => dispatch({ type: 'increment' })}
>
count is: {state.count}
</button>
</p>
</div>
);
};
const AppWithStore = () => {
return (
<ReducerContextProvider>
<App />
</ReducerContextProvider>
)
}
export default AppWithStore