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);
相关推荐
甜兒.30 分钟前
鸿蒙小技巧
前端·华为·typescript·harmonyos
她似晚风般温柔7893 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
Jiaberrr4 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy4 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白4 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、4 小时前
Web Worker 简单使用
前端
web_learning_3214 小时前
信息收集常用指令
前端·搜索引擎
Ylucius5 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
tabzzz5 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百5 小时前
Vuex详解
前端·javascript·vue.js