React hook

314 阅读4分钟

1.useState

const [     value            ,       setValue]      =      useState(0)
              ↓                           ↓                    ↓
        this.state.value           this.setState()           初始值

2.useEffect

useEffect(()=>{
    拥有componentDidMount,componentWillUpdate生命周期的功能
    return ()=>{
        拥有componentWillUnmount(卸载
    }
},[一个字段,更新再执行]) 
  • 可以写多个useEffect
  • useEffect 里面使用到的state的值, 固定在了useEffect内部, 不会被改变,除非useEffect刷新,重新固定state的值
  • useEffect不能被判断包裹
  • useEffect不能被打断

3.useReducer

  • 类似redux中的reducer
        字段    入口函数                      判断的函数    
        ↑         ↑                             ↑     
const [count, dispatch] = useReducer((state = 0, {type})=>{
    switch (type) {
        case "add":
            return state+1
        case 'delete':
            return state-1
        default:
            return state;
    }
}, 0) ← 初始值

<Button onClick={()=> dispatch({type:'add'})}></Button>

4.useContext

const TestContext = React.createContext()

function Parent() {
    return <TestContext.Provider value={'test context'}></TestContext.Provider>
}

function Child() {
    return <TestContext.Consumer>
                value => <div>I am {value}}</div>
            </TestContext.Consumer>
}

function App() {
    return (
        <Parent>
            <Child />
        </Parent>)
}

5.useRef

  • 可以用来获取组件实例对象或者是DOM对象

Dom对象

  • 代码中用useRef创建了couterRef对象,并将其赋给了 标签元素 的ref属性。这样,通过访问couterRef.current就可以访问到 标签元素 对应的DOM对象。

“跨渲染周期”保存数据

  • 可以使用useRef来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染
const refContainer = useRef(initialValue);

6.Memo

  • 类似 PureComponent 不用再写 shouldComponentUpdate 生命周期函数判断参数有无变化来控制组件的渲染。
  • 但是这是用来判断prevProps和nextProps是否相等,相等返回true,组件不发生重新渲染;反之,组件重新渲染。主要用于性能优化,不要过于依赖,容易发生bug
const areEqual = (prevProps, nextProps) => {
	return prevProps.site_info === nextProps.site_info
}
const data = memo(()=>{
        return {
            ...
        }
    },[areEqual])

7.useCallback

  • 有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
const fnA = useCallback(fnB, [a])

7.useMemo

  • 简单来说就是传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。
onst [count, setCount] = useState(0);

const userInfo = useMemo(() => {
  return {
    // ...
    name: "Jace",
    age: count
  };
}, [count]);

return <UserCard userInfo={userInfo}>

注: useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。

为什么用hook

业务变得复杂之后,组件之间共享状态变得频繁,此时组件将变得非常难以理解和维护,复用状态逻辑更是难上加难。也许你会说,我们可以引入redux来管理状态呀,是的,我在之前的工作中使用redux已是常态,配合redux-thunk或者saga还可以解决异步redux的问题,但是问题是,这些的使用将极大的加大项目的复杂度和体积,而且需要按照一套严格的标准去写组件和redux,才能保证一定的可维护和可扩展性。

首先,class的方式不能很好的的打包工具压缩,并且需要编译大量的冗余代码,这是我们不希望的。其次根据以往的经验,大量的class会使热重载出现不稳定的情况。所以我们更希望使用一个使代码更易于优化的 API。

这个问题是redux社区公认的问题。所以,基于这几个痛点,我们来展开接下来的学习。

总结:

  • Hook是对函数式组件的一次增强,使得函数式组件可以做到class组件的state和生命周期。

  • Hook的语法更加简练易懂,消除了class的生命周期方法导致的重复逻辑代码,解决了高阶组件难以理解和使用困难的问题。

  • 然而Hook并没有让函数式组件能做到class组件做不到的事情,它只是让很多事情变得更加简单而已。

  • class组件并不会消失,但hook化的函数式组件将是趋势。

//防抖
import { useEffect, useRef } from 'react'

const useDebounce = (fn, ms = 30, deps = []) => {
    let timeout = useRef()
    useEffect(() => {
        if (timeout.current) clearTimeout(timeout.current)
        timeout.current = setTimeout(() => {
            fn()
        }, ms)
    }, deps)

    const cancel = () => {
        clearTimeout(timeout.current)
        timeout = null
    }
  
    return [cancel]
  }

export default useDebounce

//节流
import { useEffect, useRef, useState } from 'react'

const useThrottle = (fn, ms = 30, deps = []) => {
    let previous = useRef(0)
    let [time, setTime] = useState(ms)
    useEffect(() => {
        let now = Date.now();
        if (now - previous.current > time) {
            fn();
            previous.current = now;
        }
    }, deps)

    const cancel = () => {
        setTime(0)
    }
  
    return [cancel]
  }

export default useThrottle