useReducer + useContext ≈ Redux
useContext
目的
context的中文解释是 上下文(context),useContext 就是为了解决跨组建偷传值的问题。
createContext 能够创建一个 React 的上下文(context),然后订阅了这个上下文的组件中,可以拿到上下文中提供的数据或者其他信息。
使用
1、用createContext 把值包裹起来,传给’themeContext’
2、通过父组件’themeContext.provide’传递给子组件,
<themeContext.provide value={xxx}>
</themeContext.provide>
不管多少个
3、通过useContext来使用
const theme = useContext(themeContext);
就可以取到theme.xxx 的值了
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
useReducer 的使用
目的
const [state,dispatch] = useReducer(reducer, initialArg,init)
拓展一下:
useReducer 理念和 redux 类似: (state, action) => newState的reducer,并返回当前的 state 以及与其配套的 dispatch 方法
使用场景:
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
使用
1、定义 :
const [state, dispatch] = useReducer(reducer, initialState)
state - 要用的数据
dispatch - dispatch 函数,可以理解为一个稳定标识的方法
reducer - reducer函数
initialState - 初始数据
2、定义 reducer 函数
function reducer(state,action){
switch (action.type){
case 'increment';
return {count :state.count + 1};
case 'decrement'
return {count :state.count - 1 };
default :
throw new Error();
}
}
3、用
const initialState = {count: 0};
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
**
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
**
</>
);
}
在定义的时候 ,可以直接将initialState传进去咯
const [state, dispatch] = useReducer(reducer, {count: 0});
第三个参数 init 初始化
你可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)。
理解:init 是一个讲值初始化的方法
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
这里 action.type = reset 的是
调用action.payload 即 init方法
跳过dispatch的情况:
官方解读是:
如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。(React 使用 Object.is 比较算法 来比较 state。)
需要注意的是,React 可能仍需要在跳过渲染前再次渲染该组件。不过由于 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必担心。如果你在渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化。
这个主要场景还是渲染优化,能对基本的渲染减少一点开销。
useReducer + useContext
各路说法都说useReducer + useContext 要一起用,为什么要一起用呢? 比如这里,如果需要传递方法:
<MyContext.Provider value={{ setStep, setCount, setNumber, fetchData }} />
是不是Context Provider 提供的方法越来越多,越来约臃肿。
那通过useReducer包装,通过dispatch 触发的,就能大大简化。
import React, { useReducer } from 'react';
import Child from './Child';
import { MyContext } from './context-manager';
const initState = { count: 0, step: 0, number: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'stepInc': return Object.assign({}, state, { step: state.step + 1 });
case 'numberInc': return Object.assign({}, state, { number: state.number + 1 });
case 'count': return Object.assign({}, state, { count: state.step + state.number });
default: return state;
}
}
export default (props = {}) => {
const [state, dispatch] = useReducer(reducer, initState);
const { step, number, count } = state;
return (
<MyContext.Provider value={{ dispatch }}>
<Child step={step} number={number} count={count} />
</MyContext.Provider>
);
}
因此此时子组件只需要拿到 dispatch 即可修改父组件的 state。
import React, { useContext, memo } from 'react';
import { MyContext } from './context-manager';
export default memo((props = {}) => {
const { dispatch } = useContext(MyContext);
return (
<div>
<p>step is : {props.step}</p>
<p>number is : {props.number}</p>
<p>count is : {props.count}</p>
<hr />
<div>
<button onClick={() => { dispatch({ type: 'stepInc' }) }}>step ++</button>
<button onClick={() => { dispatch({ type: 'numberInc' }) }}>number ++</button>
<button onClick={() => { dispatch({ type: 'count' }) }}>number + step</button>
</div>
</div>
);
});
参考文档:
http://www.ptbird.cn/react-createContex-useContext.html
Reducer - redux Reducer
小小地拓展一下redux,对理解useReducer有帮助,阅读材料:https://www.redux.org.cn/docs/recipes/reducers/NormalizingStateShape.html
基础概念:
(previousState, action) => newState
reducer的基本结构:
一个应用应该只有一个单一的reducer函数(理论上)
reducer第一次被调用的时候,state的是值需要传入一个默认值
reducer需要根据之前的state 和 dispatch 的action来决定做什么 (纯函数概念)
没有改变,需要返回当前的state
关于数据
大部分应用的数据类型分为三类:
1、需要修改的展示的 或者使用的数据 —域数据(domain data)
2、特定于应用某个行为的数据 — 应用状态(App state)
3、控制UI展示的 — UI状态(UI state)
回到之前说的,理论上一个应用应该有一个单一的reducer 是不可能(维护)的,Redux reducer 也仅仅是一个函数,所以将reducer中的一些逻辑拆分出去,然后在父函数中调用这个新函数。
reducer: 任何符合(prestate,action )-> newState 格式的函数
root reducer: 顾名思义
通常作为 createStore 第一个参数的函数。他是唯一的一个在所有的 reducer 函数中必须符合 (state, action) -> newState 格式的函数。
slice reducer: 一个负责处理状态树中一块切片数据的函数,通常会作为 combineReducers 函数的参数。
case function: 一个负责处理特殊 action 的更新逻辑的函数。可能就是一个 reducer 函数,也可能需要其他参数才能正常工作。
higher-order reducer: 一个以 reducer 函数作为参数,且/或返回一个新的 reducer 函数的函数(比如: combineReducers, redux-undo)。