在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。使用 React Hooks 相比于从前的类组件有以下几点好处:
- 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
- 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现
Hook 是 React 16.8.0 版本增加的新特性,可以在函数组件中使用 state
以及其他的 React 特性
。
Hooks只能在函数式组件中使用,既无状态组件(所有钩子在用时都要先引入)
1、Hook 使用规则
Hook 就是JavaScript 函数
,但是使用它们会有两个额外的规则:
1、只能在函数最外层调用 Hook
。不要在循环、条件判断
或者嵌套函数(子函数)
中调用。
2、只能在 React 的函数组件
中调用 Hook
。不要在其他 JavaScript 函数中调用。
3、在多个useState()
调用中,渲染之间的调用顺序
必须相同
。
2、计数器实例
(1)基础版
DemoContext.js (暴露一个Context的方法)
import React , {createContext}from 'react'
const DemoContext = createContext()
export default DemoContext
App.js (上级组件)
import React , {useReducer} from 'react';
import DemoContext from './DemoContext'
import App2 from './App2'
const App = (props = {}) => {
const initialState = {num1:0, num2:0, num3:0}
const reducer = (state,action) => {
// console.log('reducer' ,state,action)
switch(action.type){
case 'num1Add':
return Object.assign({},state,{num1 : state.num1 + 1})
case 'num2Add':
return {
...state,
num2 : state.num2 + 1
}
case 'num3Add':
return{
...state,
num3:state.num1 + state.num2
}
case 'numAdd':
return{
...state,
num1:state.num1+2
}
default :
return state
}
}
const [state,dispatch] = useReducer(reducer,initialState)
return (
<div style={{backgroundColor:'yellow'}}>
爷爷
<button onClick={()=>{dispatch({type:'numAdd'})}}>爷爷的按钮</button>
<DemoContext.Provider value={{state,dispatch}}>
<App2></App2>
</DemoContext.Provider>
</div>
);
};
export default App;
(注意reducer里面返回的写法不同)
App2.js (中间组件)
import React from 'react';
import App3 from './App3'
const App2 = () => {
return (
<div style={{backgroundColor:'greenYellow'}}>
儿子
<App3></App3>
</div>
);
};
export default App2;
App3.js (使用该状态组件)
import React, { useContext ,useMemo , memo} from 'react';
import DemoContext from './DemoContext'
const App3 = memo((props = {}) => {
const {state,dispatch} = useContext(DemoContext)
return (
console.log('孙子render'),
<div>
孙子
<div>num1 : {state.num1}</div>
<div>num2 : {state.num2}</div>
<div>num3 : {state.num3}</div>
<div>
<button onClick={()=>{dispatch({type:'num1Add'})}}>按钮1</button>
<button onClick={()=>{dispatch({type:'num2Add'})}}>按钮2</button>
<button onClick={()=>{dispatch({type:'num3Add'})}}>按钮3</button>
</div>
</div>
);
});
export default App3;
效果图:
(可以看出,我们使用useReducer + useContext 的组合
,能够在子组件
里面能够获取到上级组件传递过来的状态
,并且能够进行修改
)
不过发现,在子组件中点击“按钮3”的时候,虽然state.num1 + state.num2
并没有改变,但是一直触发子组件render,即使子组件是通过 React.memo 包装过的。
(2)useMemo() 优化
既然 React.memo() 无法拦截注入到 Context 的 state 的变化
,那就需要我们在组件内部
进行更细粒度的性能优化
,这个时候可以使用 useMemo()
App3.js
(去掉了 React.memo,在 return 内部通过 useMemo() 包装,并且声明了所有依赖项
)
import React, { useContext ,useMemo , memo} from 'react';
import DemoContext from './DemoContext'
const App3 = (props = {}) => {
const {state,dispatch} = useContext(DemoContext)
return useMemo(()=>{
console.log('孙子render')
return (
<div>
孙子
<div>num1 : {state.num1}</div>
<div>num2 : {state.num2}</div>
<div>num3 : {state.num3}</div>
<div>
<button onClick={()=>{dispatch({type:'num1Add'})}}>按钮1</button>
<button onClick={()=>{dispatch({type:'num2Add'})}}>按钮2</button>
<button onClick={()=>{dispatch({type:'num3Add'})}}>按钮3</button>
</div>
</div>
);
},[state.num1,state.num2,state.num3])
};
export default App3;
优化后的效果图:
从上面效果可以发现,当 state.num1 + state.num2 不变
的时候,是不会触发 return 中 DOM 的重新渲染
的。
3、颜色改变实例(网络)
状态管理
//ColorContext.js
import React ,{createContext,useReducer} from 'react';
const ColorContext = createContext({})
const reducer = (state,action) => {
console.log('reducer',state)
switch(action.type) {
case 'UPDATE_COLOR':
return action.color
default:
return state
}
}
const Color = (props) => {
const [color, dispatch] = useReducer(reducer, 'blue')
return (
<div>
<ColorContext.Provider value={{color, dispatch}}>
{props.children}
</ColorContext.Provider>
</div>
);
};
export {
Color,
ColorContext
};
总index.js组件
import React from 'react';
import ShowArea from './ShowArea'
import Buttons from './Buttons'
import {
Color
} from './ColorContext'
const Demo1 = () => {
return (
<Color>
<ShowArea />
<Buttons />
</Color>
);
};
export default Demo1;
按钮Button.js
import React ,{useContext}from 'react';
import {ColorContext} from './ColorContext'
const Button = (props) => {
const {dispatch} = useContext(ColorContext)
return (
<div>
<button onClick={()=>{dispatch({type:'UPDATE_COLOR',color:'red'})}}>红色</button>
<button onClick={()=>{dispatch({type:'UPDATE_COLOR',color:'yellowGreen'})}}>绿色</button>
</div>
);
};
export default Button;
颜色显示ShowArea.js
import React , {useContext}from 'react';
import {ColorContext} from './ColorContext'
const ShowArea = (props) => {
const {color} = useContext(ColorContext)
return (
console.log('颜色显示组件',color),
<div style={{color: color}}>
字体颜色展示为blue
</div>
);
};
export default ShowArea;
效果图:
4、总结
useContext 创建全局状态,不用一层一层的传递状态
。useReducer 创建 reducer
,并根据不同的 dispatch 更新 state
。- 代码
写到哪里状态就加到哪里
,不用打断思路跳到 redux 里面去写。 全局状态分离
,避免项目变大导致 Redux 状态树难以管理。