React Hook 笔记总结

原文地址: https://zh-hans.reactjs.org/blog/2020/05/22/react-hooks.html
在本文中,我们给函数式组件的函数起个简单一点的名字:render 函数。
在本文中,为了方便描述,对于 render 函数的每次调用,我想称它为一帧。

每一帧拥有独立的变量

function Example(props) {
    const { count } = props;
    const handleClick = () => {
        setTimeout(() => {
            alert(count);
        }, 3000);
    };
    return (
        <div>
            <p>{count}</p>
            <button onClick={handleClick}>Alert Count</button>
        </div>
    );
}

父组件会每秒更新count的值, 当子组件点击Alert Count后, 看到的不是最新的值, 而是3秒前的值, 原因就是: count在每一帧中是独立的, 它一旦在某一帧点击, 它只能看到它那帧的值, 最新帧的值它是获取不到的.

class Example2 extends Component {
    handleClick = () => {
        setTimeout(() => {
            alert(this.props.count);
        }, 3000);
    };

    render() {
        return (
            <div>
                <h2>Example2</h2>
                <p>{this.props.count}</p>
                <button onClick={this.handleClick}>Alert Count</button>
            </div>
        );
    }
}

然而, 使用class组件取到的是最新帧的值, 因为countthis.props中, 当父组件count刷新后, this.props也会更新, 获取this.props.count还是最新的值

状态

const [state, setState] = useState(initialState);
  • 可以通过 useState 等方式拥有局部状态
  • 如果是被创建而不是重用, 即在组件的第一帧中, 改状态的值将赋予初始值initialState, 而重用即第二帧及其后不被赋予初始值
  • 状态也是函数作用域下的普通变量。我们可以说每次函数执行拥有独立的状态。
  • 在当前帧调用setState, 在下一帧才能获取值

获取过去或未来帧中的值

const refContainer = useRef(initialValue);
  • 在组件的第一帧中,refContainer.current 将被赋予初始值 initialValue,之后便不再发生变化。
  • 设置它的值不会重新触发 render 函数
  • ref在每一帧都是共享的
  • 在当前帧设置值, 可以立刻获取值

每一帧可以拥有独立的 Effects

  • 对于 useEffect 来说,执行的时机是完成所有的 DOM 变更并让浏览器渲染页面后
  • useEffect的返回值会在当前帧结束时运行

在比对中执行 Effects

  • 如果你指定了一个 依赖列表 作为 useEffectuseMemouseCallbackuseImperativeHandle 的最后一个参数,它必须包含了回调函数中的所有值.
  • 因为不管是props还是state还是变量(以下统称变量), 在不同帧之间都是不可见的, 当effect的依赖列表中未指定回调中使用的变量,这个变量被修改后, effect并不知道, 它仍然停留在之前某一帧中, 那么这个回调中的变量不能被保证是最新值, 导致出现bug.
function Example({ someProp }) {
  function doSomething() {
    console.log(someProp);
  }

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

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

Effect 的依赖频繁变化

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // 这个 effect 依赖于 `count` state
    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Bug: `count` 没有被指定为依赖

  return <h1>{count}</h1>;
}

指定 [count] 作为依赖列表就能修复这个 Bug,但会导致每次改变发生时定时器都被重置。所以我们可以使用 setState 的函数式更新形式.

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
    }, 1000);
    return () => clearInterval(id);
  }, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量

  return <h1>{count}</h1>;
}

使用 useMemo/useCallback

  • 详细用法参考: useMemo与useCallback使用指南
  • useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
  • useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。

耗时组件性能优化

Edit on CodeSandBox

function Example(props) {
    const [count, setCount] = useState(0);
    const [foo] = useState("foo");

    const main = (
        <div>
            <Item key={1} x={1} foo={foo} />
            <Item key={2} x={2} foo={foo} />
            <Item key={3} x={3} foo={foo} />
            <Item key={4} x={4} foo={foo} />
            <Item key={5} x={5} foo={foo} />
        </div>
    );

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>setCount</button>
            {main}
        </div>
    );
}

假设 组件,其自身的 render 消耗较多的时间。默认情况下,每次 setCount 改变 count 的值,便会重新对 进行 render,其返回的 React Elements 中3个 也重新 render,其耗时的操作阻塞了 UI 的渲染。导致按下 “setCount” 按钮后出现了明显的卡顿。

为了优化性能,我们可以将 main 变量这一部分单独作为一个组件 ,拆分出去,并对 使用诸如 React.memo , shouldComponentUpdate 的方式,使 count 属性变化时, 不重复 render。

function Example(props) {
    const [count, setCount] = useState(0);
    const [foo] = useState("foo");
    // 使用 useMemo缓存结果
    const main = useMemo(() => (
        <div>
            <Item key={1} x={1} foo={foo} />
            <Item key={2} x={2} foo={foo} />
            <Item key={3} x={3} foo={foo} />
            <Item key={4} x={4} foo={foo} />
            <Item key={5} x={5} foo={foo} />
        </div>
    ), [foo]);

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>setCount</button>
            {main}
        </div>
    );
}

如何惰性创建昂贵的对象?

useState 可以通过传入函数解决

function Table(props) {
  // ⚠️ createRows() 每次渲染都会被调用
  const [rows, setRows] = useState(createRows(props.count));
  // ...
}
function Table(props) {
  // ✅ createRows() 只会被调用一次
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}

useRef 不会 像 useState 那样接受一个特殊的函数重载。相反,你可以编写你自己的函数来创建并将其设为惰性的

function Image(props) {
  // ⚠️ IntersectionObserver 在每次渲染都会被创建
  const ref = useRef(new IntersectionObserver(onIntersect));
  // ...
}
function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver 只会被惰性创建一次
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }

  // 当你需要时,调用 getObserver()
  // ...
}

受控与非受控

useReducer部分state共享帧

  • useState在不同帧之间是不可见的, 而useReducer可以保证部分state之间不同帧可见
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值