说一下常用的hooks?
这道题是一道开放题, 只能说出来名字只是最初级的答案.
能具体说出每个hook的使用场景并结合有关联的hook进行对比回答才算是一个合格的答案
文章目录
useState
useState的异步问题
react hooks
和类组件一样都有setState
的异步问题
在react内部实现中, 维护了一个 isBatchingUpdate
变量, 来判断是否批量更新; 总的来说, 在react hooks中最大的不同是
在类组件中可以通过setTimeout来同步获取到更新后的值, 但是在hooks中无法获取到; 只能通过useEffect做一些副作用处理
具体解释可以参考这一篇文章, 写的比较详细
从react原理角度理解setState究竟是同步还是异步以及react hooks中的更新机制
useState为什么返回是一个数组而不是对象
在解构赋值中, 数组的解构赋值是这样的
const foo = ['one', 'two', 'three'];
const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
只要保持位置一一对应即可
但是由于对象中的内容是没有顺序的, 所以对象无法通过顺序对应来进行解构赋值, 所以只能通过键名
const user = {
id: 42,
is_verified: true
};
const { id, is_verified } = user;
console.log(id); // 42
console.log(is_verified); // true
所以useState使用数组进行解构赋值, 可以对变量进行命名, 只需要保持顺序对应好即可;
但是如果使用对象的话, 只能使用固定的名字, 而我们通常需要使用多个state, 这样就需要对新的state重新命名, 提高了复杂度
但是其实使用array也会有对应的问题
- 强顺序, 第一个参数为state, 第二个参数为setState
- 如果只想使用第二个参数, 必须要这样
const [,setState] = useState()
- 返回的参数不能太多, 不然会变复杂
useEffect
useEffect用法
useEffect
有两个参数, 第一个参数是回调函数, 第二个参数是一个数组作为依赖项;
- 如果第二个参数不传, 则每次组件更新(渲染)都会执行一遍
- 如果第二个参数传一个空数组, 只有在首次渲染时执行, 类似于
componentDidmount
, 但是其实执行时机是不一样的 - 如果第二个参数里面有依赖项, 则在首次和依赖项改变时会执行(注意!首次也会执行)
- 如果在回调函数中
return一个函数
, 组件每次重新渲染的时候都会先执行该函数再调用回调函数
useEffect && useLayoutEffect的区别
useEffect和useLayoutEffect的执行时机不一样
- useEffect 在渲染时是异步执行,并且要等到浏览器将所有变化渲染到屏幕后才会被执行。
- useLayoutEffect 在渲染时是同步执行,其执行时机与
componentDidMount,componentDidUpdate
一致, 会阻塞浏览器渲染
useReducer
useReducer 接受一个 reducer 函数作为参数,reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的
function TestReducer() {
const reducer = (state, action) => {
switch (action) {
case 'add':
return state + 1
case 'double':
return state * 2
}
}
const [count, dispatch] = useReducer(reducer, 0)
return (
<div>
{count}
<button onClick={() => dispatch('add')}>点击添加</button>
<button onClick={() => dispatch('double')}>点击*2</button>
</div>
)
}
用useReducer实现一个useState
所以, useReducer
是useState
的加强版, 更可以用useReducer
来实现一个useState
下面是笔者的简易实现
function MyUseState(initV) {
const reducer = (state, action) => {
return action
}
const [myState, setMyState] = useReducer(reducer, initV)
return [myState, setMyState]
}
function TestUseStateUseReducer() {
const [age, setAge] = MyUseState(50)
return (
<div>
{age}
<button onClick={() => setAge(18)}>点击年龄变18</button>
</div>
)
}
useReducer+useContext实现状态管理
使用useReducer+useContext
可以实现类似redux的状态管理, 下一篇文章详细讲述
useMemo && useCallback
在react hooks性能优化中, 这两个hooks必不可少.
useMemo和useCallback接收的参数是一样的,都是在其依赖项发生变化后才执行,都是返回缓存的值
区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。
使用场景:
某些函数或者函数运行的结果
要作为参数从父组件传递给子组件的时候, 如果每次父组件更新, 这些值都会重新计算传递给子组件, 那么对于子组件来说就是一个崭新的参数, 就会触发子组件的渲染; 但是好多情况下, 子组件的这个渲染是没有必要的
所以, 使用这两个hooks可以缓存函数或函数的计算结果, 减少子组件的非必要渲染, 从而节省资源
useRef
useRef
通常被用来获取dom对象, 但不建议过多使用ref
另一种用法就是用来保存数据, react文档中有这样一句话: 当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current
属性不会引发组件重新渲染。
看这个经典的例子
function Counter() {
const [count, setCount] = useState(0)
const prevCountRef = useRef()
useEffect(() => {
console.log('useEffect-----', count)
prevCountRef.current = count
})
const addCount = () => {
setCount(count + 1)
}
return (
<div>
<button onClick={addCount}>click add count</button>
Now:{count},before:{prevCountRef.current}
</div>
)
}
- 点击按钮之后, count变化(+1)
- count变化触发组件更新重新渲染(页面更新了)
- 组件更新之后, 执行
useEffect
, 此时useEffect中拿到是这一次更新后的值, 但是界面上显示的是上次的值(下次更新显示的是这一次的prevCountRef.current
)
由于ref的变化不会触发组件重新渲染, 所以此时ref.current
已经变化了(内存中), 只是没有在页面上显示而已
usePrevious(使用useRef实现usePrevious)
function usePrevious(value) {
console.log(value)
const ref = useRef()
useEffect(() => {
console.log('in useEffect')
ref.current = value
})
console.log('ref:', ref.current)
return ref.current
}
usePrevious
被调用- 先执行
useEffect
下面的代码, 打印出ref
并return ref.current
(上一次的值, 这次传进来的下一步才会更新) - 再执行
useEffect
的回调函数, 更新ref.current=value
(此时值才会更新掉)
所以return的是上一步传进来的值, 即实现usePrevious
功能
function UsePreviousTest() {
const [count, setCount] = useState(0)
const prevCount = usePrevious(count)
const addCount = () => {
setCount(count + 1)
}
return (
<div>
<button onClick={addCount}>click to add count</button>
count:{count}
prev:{prevCount}
</div>
)
}
像上面这样调用就可以拿到上一步的count
值
PS:
上周去北京出差了, 就没更新文章. (借口)
就是发现每天写一篇文章真的做不到啊
好难啊
还是每周更新两篇吧, 应该不太难, 嗯, 就这么愉快的决定了!