每天一道面试题(15) - 常用的hooks有哪些, 具体说一下

说一下常用的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

所以, useReduceruseState的加强版, 更可以用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下面的代码, 打印出refreturn 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:

上周去北京出差了, 就没更新文章. (借口)

就是发现每天写一篇文章真的做不到啊

好难啊

还是每周更新两篇吧, 应该不太难, 嗯, 就这么愉快的决定了!

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值