hooks的几处经验之谈,踩坑

使用hooks踩过的坑

一、什么时候重新渲染页面?

状态是基础数据类型

用过react的同学应该都知道,状态改变了就会重新渲染啊,好,看下面的情况

1
2
let [update, setUpdate] = useState(false);
onClick = () => update = !update;//假设点击触发

此时页面会重新渲染吗?答案是不会
但是update状态会改变吗?答案是会
再看一个例子

1
2
let [update, setUpdate] = useState(false);
onClick = () => setUpdate(update);//假设点击触发

点击按钮后页面会重新渲染吗?答案是不会
但是update状态会改变吗?答案是不会

综上,结论是只有用setState改变状态才会触发重新渲染。

状态是数组对象

1
2
3
4
5
6
7
8
9
const [data, setData] = useState([{name: jecy}]);
...
data.map((item)=>{
<div>
<p>{item.name}</p>
<button onClick = () => item.name='tom'>enter</button>
</div>
})
...

假设我们的代码是这个逻辑,当我点击按钮页面是否会重新渲染呢?
答案是不会,你可能想说,name不是变成tom了吗,不会是因为没用setData改变状态
那看这个例子

1
2
3
4
5
6
7
8
9
10
11
12
const [data, setData] = useState([{name: jecy}]);
...
data.map((item)=>{
<div>
<p>{item.name}</p>
<button onClick = () => setData(()=>{
data[0].name='tom';
return data;
})>enter</button>
</div>
})
...

这次满足了条件,用setState的方法改变了状态,点击按钮会不会重新渲染呢?
答案是也不会,因为hooks不会检查引用类型内部数据是否发生变化,只要数组地址没有变,hooks就认为状态没变

二、useEffect注意点

什么时候执行?第一次渲染之后,和每次状态更新之后

1
2
3
4
5
const [a,] = useState(0)
useEffect(() => {
console.log('useeffect',a);
})
console.log('ok:', a);

看下输出
code
先渲染页面,然后执行useEffect里面的代码。
那代码编程这样之后输出什么呢

1
2
3
4
5
6
7
8
9
10
11
12
13
const [a, seta] = useState(0)
useEffect(() => {
const t = setTimeout(() => {
console.log('setTimeout:', a);
seta(a + 1)
}, 3000)
console.log('useEffect', a);
return () => {
console.log('clearTimeout', a)
clearTimeout(t)
};
}, [])
console.log('ok:', a);

看下输出
code
分析:

  1. 先渲染页面,所以会先执行console.log(‘ok:’, a);页面输出a的值0
  2. 执行useEffect里面的内容,根据事件循环机制会先执行console.log(‘useEffect’, a);页面输出a的值0
  3. 然后是setTimeout,等待三秒,输出a的值0
  4. seta(a + 1)状态更新,页面重新渲染,又执行了console.log(‘ok:’, a);输出1
  5. return的内容并没有执行,稍后了解为什么
    改一下代码,把依赖项去掉
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const [a, seta] = useState(0)
    useEffect(() => {
    const t = setTimeout(() => {
    console.log('setTimeout:', a);
    seta(a + 1)
    }, 3000)
    console.log('useEffect', a);
    return () => {
    console.log('clearTimeout', a)
    clearTimeout(t)
    };
    })
    console.log('ok:', a);
    应该可以发现这会无限循环,因为useState用在了useEffect中
    看一下输出
    code
    在分析一下:
  6. 先渲染页面,所以会先执行console.log(‘ok:’, a);页面输出a的值0
  7. 执行useEffect里面的内容,根据事件循环机制会先执行console.log(‘useEffect’, a);页面输出a的值0
  8. 然后是setTimeout,等待三秒,输出a的值0
  9. seta(a + 1)状态更新,页面重新渲染,又执行了console.log(‘ok:’, a);输出1,前面四个都与之前一样
  10. 状态更新页面渲染之后会创建一个新的useEffect,注意此时在调用useEffect之前会对上一个useEffect进行清理,此时,return的函数才会执行,输出被清除的useEffect中a的值也就是0
  11. 新的进入useEffect,无限循环2-6
    之前并未对useEffect中的清除函数进行深入了解,这次总算了解了它的执行顺序。
    了解了执行顺序,再看一下useEffect的精读,读后受益匪浅,总结一下:
    每次render自己的props和state都是固定不变的,可以想象每次render都生成了一个新副本,里面都是固定的常量,包括事件处理和useEffect也是固定的,比如下面这个例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const App = () => {
    const [temp, setTemp] = React.useState(5);

    const log = () => {
    setTimeout(() => {
    console.log("3 秒前 temp = 5,现在 temp =", temp);
    }, 3000);
    };

    return (
    <div
    onClick={() => {
    log();
    setTemp(3);
    // 3 秒前 temp = 5,现在 temp = 5
    }}
    >
    xyz
    </div>
    );
    };

这段代码的输出是3 秒前 temp = 5,现在 temp = 5,因为setTemp(3);执行这句话的时候又新生成一次render了,而log函数还在temp=5的那个render中,所以输出是5,这里要好好理解一下。
那怎样可以改变这个机制呢,用useRef就可以变成唯一引用,而不是每次render之间都存在隔离。

hook规则:

  1. 只在最顶层使用hook,不要在循环,条件或嵌套函数中调用 Hook
  2. 只在react中使用
上一篇

antd默认样式修改方法