React 二三事

175 阅读4分钟

React已经使用了一段时间,该去记录一些东西了

1. 什么是Hooks?

  1. 可以让你在函数组件里“钩入”React state及生命周期等特性的函数
  2. 不能在class组件中使用

1.1 有条件的渲染hooks

  useEffect(function persistForm() {
    // 👍 将条件判断放置在 effect 中
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

2. useEffect

  1. 为组件添加了操作副作用的能力;
  • 告诉React在完成对DOM的更改后运行你的“副作用”函数
  • 返回一个函数来指定如何“清除”副作用
  1. 副作用?
  • React 组件中执行过数据获取、订阅或者手动修改过 DOM

2.1 useEffect 做了什么?

  • 告诉组件需要在渲染后执行某些操作
  • React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect
  • useEffect的函数在浏览器完成布局与绘制之后,在一个延迟事件中被调用

2.2 useEffect 参数

function Example({ someProp }) {
  function doSomething() {
    console.log(someProp);  }

  useEffect(() => {
    doSomething();
  }, []); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)}

useEffect(() => {
    function doSomething() {
      console.log(someProp);    }

    doSomething();
  }, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)

2.3 effect 的依赖频繁变化,我该怎么办?

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // 这个 effect 依赖于 `count` state    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Bug: `count` 没有被指定为依赖
  
  在 `setInterval` 的回调中,`count` 的值不会发生变化。
  因为当 effect 执行时,我们会创建一个闭包,并将 `count` 的值被保存在该闭包当中,且初值为 `0`。
  每隔一秒,回调就会执行 `setCount(0 + 1)`,因此,`count` 永远不会超过 1useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // 这个 effect 依赖于 `count` state    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Bug: `count` 没有被指定为依赖
  
  指定 `[count]` 作为依赖列表就能修复这个 Bug,但会导致每次改变发生时定时器都被重置。
  事实上,每个 `setInterval` 在被清除前(类似于 `setTimeout`)都会调用一次。
  但这并不是我们想要的。
  
 useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量    
      }, 1000);
    return () => clearInterval(id);
  }, []); // ✅ 我们的 effect 不使用组件作用域中的任何变量
  
 setState 的函数式更新形式

}

3. useState

3.1 函数式更新

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState

  • 该函数将接收先前的 state,并返回一个更新后的值
 useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量    
      }, 1000);
    return () => clearInterval(id);
  }, []); // ✅ 我们的 effect 不使用组件作用域中的任何变量

4. useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值

  • 当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
  • 当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染
const value = useContext(MyContext);

4. useMemo

传入 useMemo 的函数会在 渲染期间执行

  • 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

React共享组件之间的状态逻辑的方法

  1. render props
  2. 高阶组件

处理深度更新的推荐模式

useContext + useReducer结合使用,向下传递 dispatch

import React, { useReducer, useContext } from 'react'
const MyContext = React.createContext()

const Child = () => {
  const dispatch = useContext(MyContext)
  return <MyContext.Consumer>
    <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    <button onClick={() => dispatch({ type: 'increment' })}>+</button>
  </MyContext.Consumer>
}

const contextComp = () => {
  const initState = {
    count: 0
  }
  const reducerFn = (state, action) => {
    switch (action.type) {
      case 'increment':
        return { count: state.count + 1 }
      case 'decrement':
        return { count: state.count - 1 }
      default:
        throw new Error()
    }
  }
  const [state, dispatch] = useReducer(reducerFn, initState)
  return (
    <MyContext.Provider value={dispatch}>
      Count: {state.count}
      <Child />
    </MyContext.Provider>
  )
}

export default contextComp

为什么我会在我的函数中看到陈旧的 props 和 state ?

如果你刻意地想要从某些异步回调中读取 最新的 state

import React, { useState, useRef, useEffect } from 'react'
function Example () {
  const myRef = useRef(null)
  const [count, setCount] = useState(0)

  useEffect(() => {
    myRef.current = count
  }, [count])

  function handleAlertClick () {
    setTimeout(() => {
      alert('You clicked on: ' + myRef.current)
    }, 3000)
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  )
}

export default Example

怎样阻止函数被调用太快或者太多次?

  • 节流:throttle(this.handleClick, 1000);
  1. 节流阻止函数在给定时间窗口内被调不能超过一次

  2. 基于时间的频率来进行抽样更改 (例如 _.throttle)

  • 防抖:debounce(this.emitChange, 250)
  1. 防抖确保函数不会在上一次被调用之后一定量的时间内被执行
  2. 一段时间的不活动之后发布更改 (例如 _.debounce)
  • requestAnimationFrame 节流:基于 requestAnimationFrame 的抽样更改 (例如 raf-schd)

什么是 “React Fiber”?

它的主要目的是使 Virtual DOM 可以进行增量式渲染

Hook 使用了哪些现有技术?

参考链接