在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。使用 React Hooks 相比于从前的类组件有以下几点好处:
Hook 是 React 16.8.0 版本增加的新特性,可以在函数组件中使用 state
以及其他的 React 特性
。
Hooks只能在函数式组件中使用,既无状态组件(所有钩子在用时都要先引入)
Hook 就是JavaScript 函数
,但是使用它们会有两个额外的规则:
1、只能在函数最外层调用 Hook
。不要在循环、条件判断
或者嵌套函数(子函数)
中调用。
2、只能在 React 的函数组件
中调用 Hook
。不要在其他 JavaScript 函数中调用。
3、在多个useState()
调用中,渲染之间的调用顺序
必须相同
。
const [state, dispatch] = useReducer(reducer, initialArg, init);
官网上说到useReducer是useState 的替代方案
。
它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
const [state, dispatch] = useReducer(
reducer,
initialArg,
init
);
参数一
:useReducer 接受一个reducer 函数
,reducer 接受两个参数一个是 state 另一个是 action 。参数二
:useReducer接受一个初始state
, initialArg。将初始 state 作为第二个参数传入 useReducer 是最简单的方法
。参数三
:useReducer接受一个init函数
,通过init(initialArg)来初始化 state 。这样可以惰性地创建初始 state
。由此可知,有两种
不同初始化
useReducer 的state
的方式:一种是直接在第二个参数传入初始state;另一种是在第三个参数通过init()创建初始state。
返回
一个状态state
和 dispath
方法函数,state 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的。
(两个参数)
import React ,{useReducer}from 'react';
const App2 = () => {
const initialState = { name:'张三' , num : 100 , count : 0 }
const reducer = ( state , action ) => {
switch(action.type){
case 'add':
return{
...state,
count:state.count + 1
}
case 'minus':
return{
...state,
count:state.count - 1
}
case 'reset':
return state
default :
throw new Error()
}
}
const [state, dispatch] = useReducer(reducer, initialState)
return (
console.log('aa',state),
<div>
<div>
<button onClick={()=>dispatch({type:'add'})}>加号</button>
</div>
现在的值:{state.count}
<div>
<button onClick={()=>dispatch({type:'minus'})}>减号</button>
</div>
</div>
);
};
export default App2;
(三个参数)
import React ,{useReducer}from 'react';
const App2 = () => {
const initialState = { name:'张三' , location : '北京' , count : 0 }
const init = (v) => {
console.log('v2',Object.prototype.toString.call(v)==='[object Object]') //判断是否是对象
console.log('v',v)
return v
}
const reducer = ( state , action ) => {
switch(action.type){
case 'add':
return {
...state,
count : state.count + 1
}
case 'minus':
return {
...state,
count:state.count - 1
}
case 'reset':
return init(action.payLoad)
default :
throw Error
}
}
const [state, dispatch] = useReducer(reducer, initialState , init)
return (
console.log('state',state),
<div>
<div>
<button onClick={()=>dispatch({type:'add'})}>加号</button>
</div>
现在的值:{state.count}
<div>
<button onClick={()=>dispatch({type:'minus'})}>减号</button>
</div>
<div>
<button onClick={()=>dispatch({type:'reset', payLoad : initialState})}>重置</button>
</div>
</div>
);
};
export default App2;
在某些场景下,useReducer 会比 useState 更适用
,例如 state 逻辑较复杂
且包含多个子值
,或者下一个 state 依赖于之前的 state
等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数
。
大部分人并不喜欢在组件树的每一层手动传递回调。尽管这种写法更明确,但这给人感觉像错综复杂的管道工程一样麻烦。
在大型的组件树中,我们推荐的替代方案
是通过 context 用 useReducer 往下传一个 dispatch 函数
:
const TodosDispatch = React.createContext(null);
//TodosApp为上级组件
function TodosApp() {
// 提示:`dispatch` 不会在重新渲染之间变化
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
TodosApp 内部组件树
里的任何子节点
都可以使用 dispatch 函数
来向上传递 actions
到 TodosApp:
//DeepChild为子组件树中某个组件
function DeepChild(props) {
// 如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
总而言之,从维护的角度来这样看更加方便(不用不断转发回调),同时也避免了回调的问题。像这样向下传递 dispatch 是处理深度更新的推荐模式。