ReactHooks:渲染与useState

渲染和提交

组件显示到屏幕之前,必须被 React 渲染。主要需要经历以下三个步骤:

步骤1: 触发一次渲染

有两种原因会导致组件的渲染:

  • 组件的初次渲染
  • 组件(或其父组件)的状态发生改变而触发重新渲染

当应用启动时,会触发初次渲染。一旦组件被初次渲染,就可以调用 set 函数(useState)更新其状态来触发之后的渲染。更新组件的状态会自动将一次渲染送入队列。

可以把这种情况想象成餐厅客人在第一次下单之后又点了茶、点心和其他东西,每次点单到厨房都相当于触发一次渲染。

步骤2:React 渲染组件

在触发一次渲染后,React 会调用组件来确定在屏幕上显示的内容。

这个过程是递归的,如果更新后的组件会返回某个另外的组件(子组件),React 会继续渲染这个子组件。以此类推,这个过程会持续下去,直到没有更多的嵌套组件并且 React 确切知道哪些东西应该显示到屏幕上为止。

如果更新的组件在树中的位置非常高,默认会渲染更新组件中所有的嵌套组件,这种行为可能会影响性能。

在渲染组件的过程中,React 会将更新后的虚拟 DOM 与旧的虚拟 DOM 进行对比,通过 diff 算法找出需要更新的节点,从而在提交阶段应用最少的必要操作来操作真实 DOM,提交性能。

  • 真实DOM:浏览器在解析HTML文档时,会将每个标签元素抽象成DOM节点,按照标签元素层次分明的结构,将HTML文档构建成一棵DOM树。
  • 虚拟DOM:虚拟DOM本质上是一个js对象,通过对象来表示真实的DOM结构。tag用来描述标签,props用来描述属性,children用来表示嵌套的层级关系。在状态变化时,会重新生成一个新的虚拟DOM,通过将新旧两个虚拟DOM树进行比较,只将差异的节点应用在真实DOM树上。

步骤3:React 把更新提交到 DOM 上

在渲染组件之后,React 仅在渲染之间存在差异时才会更改 DOM 节点。在渲染完成并且 React 更新 DOM 之后,浏览器就会重新绘制屏幕。

state

想要页面对输入做出反应,需要设置 state 触发重新渲染。

state 如同一张快照,React 在调用(渲染)组件时,组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照,其中所有的值都是根据这一次渲染中 state 中的值计算出来的。

一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。

js 复制代码
const [count, setCount] = useState(0);

const onClick = () => {
  setCount(count + 1);
  setCount(count + 1);

  setTimeout(() => {
  	alert(count);
  }, 3000);
}
// 最终结果:点击按钮之后,页面触发重新渲染 count会变为 1,3秒后 alert显示内容为 0

在这次渲染的过程中,state 变量 count 的值为 0,使用替代法可以将事件处理函数变成下面这样:

setCount(0 + 1);

setCount(0 + 1);

setTimeout(() => { alert(0) }, 3000);

除此之外,state 变量还有一个重要的特性:批处理

React 会将一系列的 state 更新加入队列,等到事件处理函数中的所有代码都运行之后再遍历队列,进行 state 更新。这使得可以一次更新多个 state 变量而不会触发太多次的重新渲染。

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

js 复制代码
setCount(count + 1);
setCount(count + 1);			// 1
// setCount(0 + 1);
// setCount(0 + 1);
js 复制代码
setCount(count + 1);
setCount(count + 5);			// 5
// setCount(0 + 1);
// setCount(0 + 5);
js 复制代码
setCount(count => count + 1);
setCount(count => count + 1);	// 2
// setCount(0 => 0 + 1);
// setCount(1 => 1 + 1);
js 复制代码
setCount(count => count + 1);
setCount(count + 5);			// 5
// setCount(0 => 0 + 1);
// setCount(0 + 5);
js 复制代码
setCount(count + 1);
setCount(count => count + 5);	// 6
// setCount(0 + 1);
// setCount(1 => 1 + 5);

state 更新

state 中可以存储任意类型的 js 值。对于基础数据类型,可以通过直接替换它们的值来触发一次渲染。对于对象或数组,则需要创建一个新的对象或数组并将其传递给 state 的设置函数。

对象:使用 ... 展开语法进行复制

js 复制代码
const [person, setPerson] = useState({
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Hamburg',
    image: 'https://i.imgur.com/Sd1AgUOm.jpg',
  }
});

setPerson({
  ...person, 	// 复制其它字段的数据 
  artwork: { 	// 替换 artwork 字段 
    ...person.artwork, 	// 复制之前 person.artwork 中的数据
    city: 'New Delhi' 	// 但是将 city 的值替换为 New Delhi!
  }
});

数组

js 复制代码
const [ products, setProducts ] = useState([
  { name: 'Baklava', count: 1},
  { name: 'Cheese', count: 5},
])

新增:... 数组展开

js 复制代码
setProducts([ 
	...products, 
	{ name: 'Spaghetti', count: 2}
]);	 // 可以加在开头或末尾

插入中间:slice

js 复制代码
setProducts([
  ...products.slice(0, 1),
  { name: 'Spaghetti', count: 2},
  ...products.slice(1),
])

删除:filter

js 复制代码
setProducts(
  products.filter(p => p.count <= 1);
)

更新:map

js 复制代码
setProducts(
  products.map(p => {
	if (p.name === 'Cheese'){
	  return { ...p, count: p.count + 1};
	}
	return p;
  })
)

排序:先拷贝一份

js 复制代码
const newProducts = [ ...products];
newProducts.sort((a, b) => b.count - a.count);
setProducts(newProducts);
相关推荐
涔溪33 分钟前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞44 分钟前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇1 小时前
ES6进阶知识一
前端·ecmascript·es6
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR1 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜1 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss