useReducer
用来践行Flux/Redux的思想,总的来说是useState的复杂版
基本使用:
- 创建初始值 initialState
- 创建所有操作封装为一个函数
reducer(state,action)
- 传给useReducer,得到读和写的API
- 调用写的API:
dispatch({type:'操作类型'})
计数器案例
```javascript …
const initial = { //——————1.创建初始值initialState n:0 }
const reducer =(state,action) =>{ //———-2.封装操作 reducer(state,action) if(action.type === ‘add’){ return {n: state.n + action.number} }else if(action.type === ‘multi’){ return{n: state.n * 2} }else{ thorw new Error(‘unknown type’) } }
function App(){ const [state,dispatch] = useReducer(reducer,initial)//——-3.传给useReducer得到读写API
const {n} = state const onClick1 = ()=>{ dispatch({type:’add’,number:1})//—————4.调用 写 dispatch API } const onClikc2 = ()=>{ dispatch({type:’multi’,number:2}) }
return (
n:{n}
与useState 的应用场景区别
- 当用于管理基础类型js变量时使用useState
- 当使用object对象或者array这类引用类型时使用useReducer
如:`const [state, setState] = useState({ firstname: 'Robin', lastname: 'Wieruch' }) `时,最好使用useReduce代替useState
<a name="hK2xb"></a>
## 如何代替Redux
**结合useContext**<br />步骤:
1. 将数据集中在一个store对象
1. 将所有操作集中在 reducer
1. 创建一个 Context
1. 通过 useReducer 得到读写API
1. value绑定 读写API 传给 Context.Provider
1. 用Context.Provider 将Context 提供给所有组件
1. 各个组件用 useContext获取读写API
useReducer + useContext 代替 Redux 案例:[https://codesandbox.io/s/interesting-volhard-lfxpm?file=/src/index.js](https://codesandbox.io/s/interesting-volhard-lfxpm?file=/src/index.js)
<a name="NQd3T"></a>
### 如何模块化
其实就是 分目录划分区域导入导出即可,如图<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/12625512/1652336650565-bcf7a55f-5dc1-453a-bcf3-34c65712e4d5.png#clientId=u53170e43-9d1e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=419&id=u99666714&margin=%5Bobject%20Object%5D&name=image.png&originHeight=524&originWidth=343&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25254&status=done&style=none&taskId=u35ef4722-54a2-407f-ade7-b6eb25137a9&title=&width=274.4)<br />模块化案例代码:[https://codesandbox.io/s/priceless-jennings-gyls6?file=/src/reducers/books_reducer.js](https://codesandbox.io/s/priceless-jennings-gyls6?file=/src/reducers/books_reducer.js)<br />优化小技巧:reducer函数改造<br />在 封装所有操作的 reducer 为函数这一步骤中,函数难以分割合并,可以先借助对象进行集中化管理以便模块化
```javascript
//reducer操作逻辑都在index.js单文件中:高内聚高耦合
function reducer(state,action){
switch (action.type){
case "setUser":
return {...state,user:action.user}
case "setBooks":
return {...state,books:action.books}
case "setMovies":
return {...state,movies:action.movies}
default:
throw new Error('Unkown type!')
}
}
//reducer逻辑分模块,最后再统筹于index.js文件:高内聚低耦合
// src/reducers/books_reducer.js
export default {
setBooks:(state,action) =>{
return {...state,books:action.books}
},
deleteBook:()=>{...},
...
}
// src/index.js
const obj ={
...userReducer, //-----分模块
...bookReducer,
...moviesReducer
}
function reducer(state,action){ //-----再统筹(重点)
const fn = obj[action.type];
if(fn){
return fn(state,action) //记得return返回函数值
}else{
throw new Error('Unkown type!')
}
}
useEffect
副作用:对环境(全局变量、非组件内部状态的数据等)的改变,如修改document.title
但我们不一定非要把副作用放在useEffect 里
实际上叫做afterRender 更好,每次render 后运行
可替代类组件的生命周期钩子,作为函数组件的“生命周期钩子”
基本使用:
每次渲染:useEffect(() => {...})state中的任何数据变化都会渲染
首次渲染:[ ] 空数组作为第二个参数 useEffect(() => {...},[])
首次+往后渲染:useEffect(() => {...})
指定依赖n + 第二次开始渲染:useEffect(() => {...可添加初始值作为判断依据,避开首次渲染},[n])
卸载后渲染:useEffect(() => {...}, return ()=>{...卸载操作})
如果存在多个useEffect,会按照出现的次序执行
useEffect执行时机
以上所说的渲染均在浏览器 完成布局与绘制之后,即在DOM渲染完成之后执行useEffect 中的回调函数,
延迟调用 useEffect,因此会使得额外操作很方便
优化小技巧:
辨别:
需要清除的工作有哪些:例如订阅外部数据源。
这种情况下,清除工作是非常重要的,可以防止引起内存泄露
而无需清除的工作有:发送网络请求,手动变更 DOM,记录日志
useEffect(()=>{
const timer = setInterval(()=>{
console.log('npc在定时播放')
},1000)
},return ()=>{ //注意return 函数在useEffect 的第二个参数
window.clearInterval(id)
})
useLayoutEffect
useLayoutEffect 与 useEffect 区别:
useLayoutEffect 总是比 useEffect 先执行,它会在所有的 DOM 变更之后同步调用 effect
useLayoutEffect 里的任务放置的主要是 影响 Layout 的任务,可避免页面闪烁,但延迟了给用户看到画面的时间
然而,因为绝大多数操作不应阻塞浏览器对屏幕的更新(DOM操作本来就容易阻塞页面渲染),故尽可能使用标准的 useEffect
优化小技巧:
推荐一开始先用 useEffect(优先渲染),只有当它出问题的时候再尝试使用 useLayoutEffect(避免闪烁)
useMemo
背景:React 默认有多余的 render,即对于父子嵌套组件中,父组件数据变动,子组件数据不变的情况下,父子组件均会全部重新渲染一次(其中,子组件是不必要的render)
即子组件接收的props 不变的话,没有必要再次渲染子组件,
故可使用高阶组件React.Memo包裹原先组件
高阶组件React.Memo
它接收一个组件A作为参数并返回一个组件B,如果组件B的 props 没有改变,则组件 B 会阻止组件 A 重新渲染 (仅检查 props 变更)
const MyComponentB = React.memo(function MyComponentA(props) {
/* 使用 props 渲染 */
});
缺点:React.Memo 无法拦截 props 中的监听函数重新渲染(原因:监听函数在父组件每次渲染时会重新创建都会生成不同的新的返回值,不同于基本数据类型反复生成的值不变,即值和引用的区别)
终极方案:使用useMemo
useMemo使用
一般配合React.Memo一起使用,做到全方位复用组件,利于性能优化,避免在每次渲染时都进行高开销的计算
const memoizedValue = useMemo( () => { return ()=>{.....console.log(a,b)} }, [a, b] )
- 第一个参数是 ( ) =>{ value },value可能是对象、函数等
- 第二个参数是 [依赖项数组]
- 返回一个 memoized 值
类似vue2 的 computed计算属性(只有当依赖变化时,才会计算出新的value,如果依赖不变,那么重用之前的value)
注意:
useMemo执行时机:传入 useMemo 的函数会在渲染期间执行
请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一个 memoized 回调函数useCallback(fn, deps) === useMemo( () => fn , deps)
useRef
const refContainer = useRef(initialValue);
- useRef 返回一个可变的 ref 对象(可以用来引用DOM对象、普通对象)
- 返回的 ref 对象在组件的整个生命周期内持续存在即保持不变,不会被覆盖
- 读取:count.current
为什么需要.current?
为了保证两次useRef 指向同一个地址,useRef返回一个 ref 对象(引用),则.current 取到的是对象的value
类似 vue3 的ref
Vue3的ref的react的useRef区别:
vue3在count.value变化时,会自动render,而react在count.current变化时,需要手动render
那react的useRef能做到变化时自动render吗?
不能,因为不符合react的理念
React的理念是 UI = fn( data )
手动render :监听ref ,在 ref.current 变化时 ,调用 setX即可
高阶组件React.forwardRef
背景:props无法传递refs属性。
React.forwardRef会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中
基本用法:
- 函数组件 想要 接受 父组件传来的ref,则应该使用React.forwardRef创建函数组件
- 函数组件中接收两个形参(props和ref)
- 在该函数组件中的组件树可以绑定外部传来的ref
应用场景:
- 转发 refs 到 DOM 组件
- 在高阶组件中转发 refs
```javascript
function App(){
const buttonRef = useRef(null) //buttonRef可以引用到Button2对应的DOM对象
return (
) }
<Button2 ref = {buttonRef}>按钮</Button2>
//将父组件绑定给子组件的ref转发到子组件内部(可以给子组件内的组件树)
const Button2 = React.forwardRef((props, ref) => (
));
> 以下是对上述示例发生情况的逐步解释:
>
> - 我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
> - 我们通过指定 ref 为 JSX 属性,将其向下传递给 <FancyButton ref={ref}>。
> - React 传递 ref 给 forwardRef 内函数 (props, ref) => ...,作为其第二个参数。
> - 我们向下转发该 ref 参数到 <button ref={ref}>,将其指定为 JSX 属性。
> - 当 ref 挂载完成,ref.current 将指向 <button> DOM 节点。
进阶:ref的两次传递得到button 的引用<br />[https://codesandbox.io/s/amazing-snow-9f5g3](https://codesandbox.io/s/amazing-snow-9f5g3)
<a name="PfzQ5"></a>
# useImperativeHandle
可以让你在使用 ref 时子组件内自定义暴露给父组件的实例值“setRef”<br />常与 forwardRef 一起使用
```javascript
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => { //对ref进行改装,即setRef
return { //返回一个对象
focus: () => {
inputRef.current.focus();
}
}});
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput); //forwardRef