谈谈react 渲染(render)

126 阅读3分钟

每一次渲染都有属于函数组件自己的Props和State

举个计数器栗子:

function Counter() {
  const [n, setN] = useState(0);

  return (
    <div className="App">
      <p>You clicked {n} times</p>	
      <button onClick={() => setN(n + 1)}>+1</button>	
    </div>
  );
}

点击按钮setN后,n加1.你可能会认为实现该功能的原因是n可能是“data binding”, “watcher”, “proxy“或者其他的东西.n会“监听”状态的变化并自动更新.但是事实上n只是一个普普通通的数字而已.

类似于这样:

const n = 0

<p>You clicked {n} times</p>

当组件第一次渲染时,从useState(0)拿到n的初始值为0,当调用setN(n+1)后React会再次渲染组件,这一次从useState拿到n1 类似于这样

// first render
function Counter() {
  const n = 0; // Returned by useState()  // ...
  <p>You clicked {n} times</p>
  // ...
}

// After a click, second render our, function is called again
function Counter() {
  const n = 1; // Returned by useState()  // ...
  <p>You clicked {n} times</p>
  // ...
}

// After another click, third render, our function is called again
function Counter() {
  const n = 2; // Returned by useState()  // ...
  <p>You clicked {n} times</p>
  // ...
}

当我们更新状态的时候,react会重新渲染组件.每一次渲染都能拿到独立的n 状态,这个状态值是函数中的一个常量。react会更新DOM以保持和渲染输出一致。 因此无任何数据绑定的操作

关键点在于任意一次渲染中的n常量都不会随着时间改变。渲染输出会变是因为我们的组件被一次次调用,每一次调用引起的渲染中,它包含的n值独立于其他渲染。

每一次渲染都有他自己的事件处理函数

举个栗子:

  1. 操作一:点击 +1 在点击 log
  2. 操作二:点击 log 再点击 +1
function Counter() {
  const [n, setN] = React.useState(0);
  const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
  
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}

操作二log出了旧数据?原因是log捕获了按钮点击时的状态.

我们知道组件函数每次渲染都会被调用,但是每一次调用中n值都是常量,并且它被赋予了当前渲染中的状态值。函数也有类似行为.

先看一个简单的栗子

function sayHi(person) {
  const name = person.name;  
  setTimeout(() => {
    alert('Hello, ' + name);
  }, 3000);
}

let someone = {name: 'Dan'};
sayHi(someone);

someone = {name: 'Yuzhi'};
sayHi(someone);

someone = {name: 'Dominic'};
sayHi(someone);

外层的someone会被赋值很多次(就像在React中,当前的组件状态会改变一样)。然后,在sayHi函数中,局部常量name会和某次调用中的person关联。 因为这个常量是局部的,所以每一次调用都是相互独立的。结果就是,当定时器回调触发的时候,每一个alert都会弹出它拥有的name

类似于这样

// first render
function Counter() {
  const n = 0; // Returned by useState()  // ...
  const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
  <p>You clicked {n} times</p>
  // ...
}

// After a click, second render our, function is called again
function Counter() {
  const n = 1; // Returned by useState()  // ...
  const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
  <p>You clicked {count} times</p>
  // ...
}

这就解释了我们的事件处理函数如何捕获了点击时候的n值。也就是说事件处理函数“属于”某一次特定的渲染,当你点击的时候,它会使用那次渲染中n的状态值。

在任意一次渲染中,props和state是始终保持不变的。 如果props和state在不同的渲染中是相互独立的,那么使用到它们的任何值也是独立的(包括事件处理函数)。

每一次渲染都有他自己的Effects

先看个简单的栗子

function Counter() {
  const [n, setN] = React.useState(0);
  useEffect(() => console.log(`n: ${n}`));
  
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}

每次setN后就会log出对应的n. 那么effect是如何读取到最新的n 状态值的呢?

effect是否像我们当初猜想n是“data binding”, “watcher”, “proxy“一样存在某种监听机制使得n能够在effect函数内更新?也或许n是一个可变的值,React会在我们组件内部修改它以使我们的effect函数总能拿到最新的值?

都不是。

我们已经知道在任意一次渲染中,props和state是始终保持不变的。n是某个特定渲染中的常量。事件处理函数“看到”的是属于它那次特定渲染中的n状态值。对于effects也同样如此:

并不是n的值在“不变”的effect中发生了改变,而是effect 函数本身在每一次渲染中都不相同。effect“看到”的是属于它那次特定渲染中的n状态值。

以下栗子更能体现 每一次渲染都有他自己的Effects,effect能捕获属于它那次特定渲染中的n状态值.

function Counter() {
  const [n, setN] = React.useState(0);

  useEffect(() => {    setTimeout(() => {   
  console.log(`You clicked ${n} times`);    }, 3000);  });
  
   return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}

操作:持续点击3次按钮,3秒后依次打印出You clicked 1 times;You clicked 2 times;You clicked 3 times

总结

  • 每一次渲染都有他自己的所有。
  • 每一个组件内的函数(包括事件处理函数,effects,定时器或者API调用等等)会捕获某次渲染中定义的props和state。
  • 在单次渲染的范围内,props和state始终保持不变。

如果想在effect的回调函数里读取最新的值而不是捕获的值。最简单的实现方法是使用refs,