官方
useRef
当您希望组件“记住”某些信息,但又不希望该信息触发新的渲染时,您可以使用 ref.
什么意思呢 ? 我自己定义一个对象不能 “记住” 某些信息吗 ? 如下的代码
import { useState } from "react";
export default function Counter() {
const obj = { current: "折木" };
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
obj.current = `zhemu ${Math.random()}`;
}
console.log("what is obj now", obj);
// 每一次re-render都是{ current: "折木" };
return <button onClick={handleClick}>You clicked {count} times</button>;
}
但是当你点击按钮试图修改 obj 这个对象的current 属性 时, 在 下一次
Counter re-render中, 你会发现obj还是最初的样子, what, 这跟我们期望的有点差距奥, 原因很简单, 函数组件的每次调用, 内部的变量, 函数都会被重新创建, 为了解决这个问题, 我们应该使用useRef , useRef 会在每次渲染时返回同一个 ref 对象, 它永远不会改变
为了更好的理解这句话的意思, 我们来看一个例子, 是关于useEffect的奥, 链接
import { useState, useEffect } from "react";
export default function Demo() {
const [count, setCount] = useState(0);
const options = { step: 2 };
useEffect(() => {
console.log("render");
}, [options]);
function handleClick() {
setCount(count + options.step);
}
return <button onClick={handleClick}>You clicked {count} times</button>;
}
现象是什么? 每点击一次总会打印 render
, 让我们回顾下代码, 每当我们点击设置了 count, Demo组件重新渲染, options 重新初始化, 变成 新
的了, useEffect
回调重新执行了, 我们知道 useEffect
只有当 “依赖变化” 时才会执行, 但是, 它内部使用的是Object.is, 来看看
js对象是mutable
在javascript中,对象是一种引用类型,他存在内存的堆中,当你 let obj = {} 就相当于在栈中创建了一块区域用来存放obj这个变量,然后让这个变量指向堆中存放 {} 的位置,这也是为什么下面这段代码打印false的原因
var a = {}
var b = {}
console.log(a === b); // false
那原因很清楚了,我们怎样去改造一下useEffect, 使得当依赖(对象或者数组) 真正变化
的时候, 才去执行呢?
思路很明显, 我们定一个ref变量 为0之类的, 当深比较上一次依赖引用类型跟最新的依赖引用类型不一致时, ref 变量变化一下, 将这个ref变量当作useEffect的依赖, 这样就可以了
function useDeepCompareEffect(effect, deps) {
const signalRef = useRef(0);
if (deps === undefined || !isEqual(上一次deps, deps)) {
signalRef.current += 1;
}
useEffect(effect, signalRef.current);
}
原因
想到这里,还有一个疑问点,怎么知道上一次的依赖呢 ? 很简单, 我们使用了全局变量,在eatWhat第一次执行时,保存这次的值, 第二次执行时,判断上一次保存在全局变量的值是否跟传进来最新的一致,从而决定是否更新
let previousFood = null;
function eatWhat(food) {
if (!Object.is(food, previousFood)) {
console.log(`上一次你问的是什么${previousFood}吧!`);
previousFood = food;
}
console.log(`今天去吃${food}吧!`);
}
eatWhat("汉堡王🍔");
eatWhat("炒菜");
那在FC的场景下,一样的,只不过我们需要使用 useRef
const isPrimitive = (val: any) => val !== Object(val);
export const useDeepCompareEffect = (effect: EffectCallback, deps: any[]) => {
if (process.env.NODE_ENV !== 'production') {
if (!deps || !deps.length) {
console.warn(
'`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.',
);
}
if (deps.every(isPrimitive)) {
console.warn(
'`useDeepCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.',
);
}
}
const ref = useRef<DependencyList | undefined>(undefined);
if (!ref.current || !isEqual(deps, ref.current)) {
ref.current = deps;
}
useEffect(effect, [ref.current]);
};
好漫长, useRef的例子还有好多, 例如在异步回调中读取最新状态 , 使用 React Hooks 使 setInterval 声明式
useEffect
一个勾子, 当组件被渲染到屏幕后
去执行一些 副作用
, 通常是数据请求, 手动变更DOM, 记录日志等…
主要需要明确几个点,
- useEffect 是在组件commit 后触发的勾子回调, (也就是将render阶段收集到的fiber树🌲转为dom的过程)
副作用
的含义理解, react 你可以看作是一个 f(state, props) => ui 的一个纯函数 , 这样设计是为了确保组件的渲染可控, 你可以简单理解跟这个更好的获取数据的方式