关于hook api 介绍的本文不会详细说,毕竟官网写的很详细,而且讲的比我好。
本文主要讲三个方面:
- 关于
hooks
的一些‘奇怪表现’ -
其他
hook api 使用场景 - hooks 性能优化总结
因本人使用 hooks api 也只有三个月时间,实际上入手 react 栈也只有三个月,所以本文也没啥深度,适合刚接触 hooks 的同学。
关于 hooks
组件不得不了解的事情
- 当
hooks
组件数据变化触发渲染时,其实是整个组件函数重新执行一次。 - 既然整个组件函数重新执行一次,那组件里如果使用普通变量自然在每次重新执行的时候重新初始化了。
所以在 hooks 中不能使用普通变量
,这也是刚开始使用hooks时容易造成困惑的地方。 - 子组件 state 更新不会影响到父组件;父组件 state 更新会导致子孙组件重新执行。
其他
hook api 使用场景
这里的其他
指的是官网没有花大篇幅介绍的 api,但是api也不少,我刚开始接触的时候就发现要搞清楚这些api 还是要花不少时间去理解的。如果能把每个api,所以我这里就先能把每个 api 的使用场景或特点先简单介绍一下,以便更快的使用或理解 api。
useReducer
- 使用场景1
useReducer
是 useState
加强版, 适合逻辑复杂或结构复杂的 state;或者 state 更改时依赖上一个 state;不过个人觉得这方面 useState
也可以做到,useReducer
优势并不是很明显。
- 使用场景2
子孙组件里要更改父组件 state 值时很好用.
这个场景里,一般我们会把 useState
里的 ‘setState’ 传给子组件,如果是多级组件那就要一层一层地传递了,太麻烦了!对于这个问题我们可以使用 useContext
+ useReducer
来解决,代码如下:
// utils.js
import { createContext } from "react";
export const TestDispatch = createContext(null);
// parent.js
import React, { useReducer } from "react";
import Son from './son';
import { TestDispatch } from './utils';
export default function UseReduce() {
const [state, dispatch] = useReducer((state, action)=> {
return {...state, ...action};
}, {
num: 1,
show: true
});
return (
link{state.num}
)
};
// son.js
import React, { useContext } from "react";
import { TestDispatch } from './utils';
export default function Son(props) {
const {num, show} = props;
const dispatch = useContext(TestDispatch);
return (
)
};
useMemo
与 useCallback
- 使用场景1
根据某个属性值计算产生新的值,并且属性值变化也跟着变化
如果是玩过 vue 的同学,一定对 computed
这个不陌生,每一个 computed
属性都是由依赖属性计算而生成的一个值,当依赖属性变化时返回新的计算的值,无变化则返回缓存值。比如我们定义一个 computed
属性 b,b 依赖 a 值,表达式为 b = a+1
,当a为1时;b就是2,a为2时,b就是3。当依赖值a在很多地方变化时computed属性会特别方便。
在 react class 里是没有 computed
这一概念的,所以有时候出现依赖关系的属性是很麻烦,需要我们在 每个 a 变化的地方再手动更新一下 b。
而 hooks 里的 useMemo
就是 computed
的替代用法,它有两个参数,第一个参数是 函数, 第二个参数是 函数里要用到的 state 或者 props值,用法如下,当num加到2的时候颜色就相应变化:
import React, { useCallback, useMemo, useState } from "react"
export default function UseCallback() {
const [num, changeNum] = useState(1);
const color = useMemo(
()=> {
switch (num) {
case 2:
return 'red';
case 3:
return 'blue';
default:
return 'black';
}
},
[num]
);
return (
{num}
)
}
useCallback
为啥要和 useMemo
放一起,是因为它两基本一致。useMemo
返回的是缓存的数据,而 useCallback
返回的是缓存的函数,所以上面的例子用 useCallback
的话,color 那块就得作为函数再执行一次:
{num}
useMemo
和 useCallback
返回的是缓存数据或者缓存函数,那自然是可以作为性能优化的手段。只要依赖的 state 或 props值不变化,那在组件重新渲染的时候它两返回的数据还是缓存数据或函数。
useMemo
与useCallback
使用的注意事项:与渲染无关的操作,诸如副作用这类的操作应在useEffect
里去做,而不是使用useMemo
或useCallback
。
useRef
- 使用场景1
看到 ref 就知道它大概能干嘛了,没错,它可以用来访问 DOM。useRef
的值保存在它的 .current
属性里,更改和使用都是利用 .current
属性。
import React, { useRef } from "react"
export default function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
>
);
}
- 使用场景2
它的可保存值不仅限于 DOM,你可以用它来保存任何可变值。
前面说过每次组件数据变化导致重新渲染时,函数组件会重新执行一次,这时函数组件里定义的普通变量就会重新定义(上一个生命周期里的值就丢了),所以当我们需要一个不受生命周期影响的变量时可以使用 useRef
。
import React, { useState, useRef } from "react"
export default function UseState() {
const [state, changeState] = useState(0);
const list = useRef([]);
const _changeState = ()=> {
const newState = state+1;
changeState(newState);
list.current.push(newState);
}
return (
{state}-{list.current.length}
)
}
useRef
特殊性是 当 ref 对象内容发生变化时,useRef
并不会通知你。变更.current
属性不会引发组件重新渲染。上面的 list.current.length
在 state 变化时也重新渲染只是搭上 state 变化引起渲染的便车而已。下面代码可以解释这个问题,click 事件里 current 变化并不会导致组件重新渲染:
import React, { useEffect, useRef } from "react"
export default function UseRef() {
console.log('change');
const ref = useRef('this is ref');
useEffect(()=> {
document.body.addEventListener('click', ()=> {
ref.current = 'ref change';
})
})
return (
{ref.current}
)
}
自定义hook
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
自定义hook 一般返回的是state。另一个必须注意的点是自定义hook名必须以 use
开头。
import { useState, useEffect} from 'react';
// custom hooks to listen window width change
function useWindowWidth(){
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = ()=>{
setWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
}, [width]); // width 没有变化则不处理
return width;
}
还有几个使用场景很少的 hooks 就不介绍了。
推荐一个自定义 hooks 库集合: https://github.com/streamich/...
hooks 性能优化
useEffect
优化
- 增加第二个参数防止重复执行。
useEffect
第二个参数是数组,当为空的时候,只初始化时执行一次,即便父组件重新渲染useEffect
也不再执行第二次;当第二个参数不为空,那只有在数组里的值有变化时useEffect
才会重新执行。
useEffect(()=> {
console.log('effect');
}, []);
useEffect(()=> {
console.log('effect');
}, [props.a, b]);
- 组件卸载时清除某些副作用,比如在
useEffect
里绑定了事件,一定要在组件卸载时再解除绑定,不然事件绑定会一直存在影响性能。
useEffect(()=> {
document.body.addEventListener('click', changeContent)
return ()=> {
document.body.removeEventListener('click', changeContent);
};
});
一个hooks函数里
useEffect
可以有多个,每个互不干扰,依次执行。
React.Memo
缓存子组件
前面说过当每次父组件渲染时,所有子孙组件都会重新渲染,但props如果没变化,子组件完全没必要重新渲染。这时候就可以使用 React.Memo
对子组件进行缓存,只有当 props 变化时子组件才会重新渲染。React.memo
基础用法很简单,把组件通过React.memo
包一层就好了。
import React from "react";
function Child(props) {
console.log(props.name)
return {props.name}
}
export default React.memo(Child)
React.memo
默认情况下只会对 props 的复杂对象做浅层对比(浅层对比就是只会对比前后两次 props 对象引用是否相同,不会对比对象里面的内容是否相同),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。具体的看下面的链接吧。
React.Memo
官方地址: https://zh-hans.reactjs.org/d...
推荐观看: https://mp.weixin.qq.com/s/ic...
useMemo
与useCallback
实现性能优化
前面 useMemo
与useCallback
的介绍里也说了它两能实现一些性能的优化,不过在有些情况下我们也需要再优化下,比如父组件的useState
的 setState 传给子组件时,当父组件重新渲染时,子组件即便用了 React.Memo
也会重新渲染,这是因为 setState 也不是原来的函数了,也需要使用 useCallback
实现这个函数缓存。具体可见上面React.Memo
最下面的 推荐观看
链接。
有收获再接着写...