添加交互性
- 1. 事件传播
- 1.1 停止传播
- 1.2 阻止默认事件
- 2. [Hook] useState 状态
- 3. 渲染和提交
- 3.1 触发渲染
- 3.2 React渲染组件
- 3.3 提交对 DOM 的更改
- 3.4 浏览器绘制
- 4. 渲染快照
- 状态队列例子
- 5. 更新state中的对象
1. 事件传播
js的事件流:
- 事件捕获:从外到内
- 事件冒泡:从内到外(默认模式)
export default function Toolbar() {return (<div className="Toolbar" onClick={() => {alert('You clicked on the toolbar!');}}><button onClick={() => alert('Playing!')}>Play Movie</button><button onClick={() => alert('Uploading!')}>Upload Image</button></div>);
}
点击任一按钮,它的 onClick 将首先运行,然后是父节点
如果你点击灰色区域本身,只有父
1.1 停止传播
为阻止传播,可以在onClick中加一行引用事件参数e并调用e.stopPropagation()
方法,此时当用户点击<button>
时触发器该条语句,停止事件向外扩散,父节点<div>
的onClick将不再触发。
1.2 阻止默认事件
e.preventDefault()
阻止默认行为
<form>
:当内部按钮点击后,将默认重载整个页面
export default function Signup() {return (<form onSubmit={e => {e.preventDefault(); //点击按钮后将不再重载页面alert('Submitting!');}}><input /><button>Send</button></form>);
}
2. [Hook] useState 状态
形式:const [index, setIndex] = useState(默认值)
“[]”从useState
解构出来两部分,
- 用于保留渲染之间数据的状态变量
index
- 一个状态设置函数,
setIndex
用于更新变量并触发 React 再次渲染组件。
只能使用在组件,不能在条件、循环或其他嵌套函数中调用 Hooks,hook调用原理:
React报错#310复盘小结+hooks使用的场景+调用原理只有 Hook 的调用顺序在多次渲染之间保持一致,React 才能正确地将内部 state 和对应的 Hook 进行关联。
useState内部:
React 为每个组件保存一组状态对。 它还维护当前对索引,该索引在渲染前设置为 0。 每次调用 useState 时,React 都会为您提供下一个状态对并递增索引。
let componentHooks = [];
let currentHookIndex = 0;
// How useState works inside React (simplified).
function useState(initialState) {let pair = componentHooks[currentHookIndex];if (pair) {// This is not the first render,// so the state pair already exists.// Return it and prepare for next Hook call.currentHookIndex++;return pair;}// This is the first time we're rendering,// so create a state pair and store it.pair = [initialState, setState];function setState(nextState) {// When the user requests a state change,// put the new value into the pair.pair[0] = nextState;updateDOM();}// Store the pair for future renders// and prepare for the next Hook call.componentHooks[currentHookIndex] = pair;currentHookIndex++;return pair;
}
3. 渲染和提交
举个例子:
组件是厨房里的厨师,用食材烹制美味佳肴。React 是一个服务员,负责接收客户的订单并为他们带来菜肴。这个请求和提供 UI 的过程分为三个步骤:
- 触发渲染(将客人的订单送到厨房)
- 渲染组件(在厨房准备订单)
- 提交给 DOM(将制作好的菜肴放在客户的桌上)
3.1 触发渲染
组件渲染有两个原因:
-
初始渲染
root.render(<App />);
-
组件(或其祖先之一)的state已更新。
3.2 React渲染组件
export default function Gallery() {return (<section><h1>Inspiring Sculptures</h1><Image /><Image /></section>);
}
function Image() {return (<img src="xxx" alt="xxx" />);
}
-
在初始渲染时,React 将调用根组件。
为<section>
、<h1>
和两个<img>
标签创建DOM节点 -
对于后续的渲染,React 将调用状态更新触发渲染的函数组件。
React 将计算它们的哪些属性(如果有)自上次渲染以来发生了变化。 在下一步,即提交阶段之前,它不会对这些信息做任何事情。
3.3 提交对 DOM 的更改
- 初始渲染时,React 将使用
appendChild()
DOM API 将它创建的所有 DOM 节点放在屏幕上。 - 对于重新渲染,React 将应用最少的必要操作(在渲染时计算!)以使 DOM 匹配最新的渲染输出。
如果渲染之间存在差异,React 只会更改 DOM 节点。
举例:
Clock组件会随着父节点传入不同的props 参数 time 而重新渲染,但在input中输入‘1’,重新渲染时‘1’没有消失。
export default function Clock({ time }) {return (<><h1>{time}</h1><input /></>);
}
这是因为随着time的改变, React 只更新了<h1>
的内容,而没有触及<input>
3.4 浏览器绘制
渲染完成并且 React 更新 DOM 后,浏览器将重新绘制屏幕
4. 渲染快照
当 React 重新渲染一个组件时:
- React 再次调用你的函数。
- 您的函数返回一个新的 JSX 快照(使用该渲染中的state计算的,在快照中保持状态值“固定”)
- React 然后更新屏幕以匹配您返回的快照。
setState
只会为下一次渲染更改它
export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 1);setNumber(number + 1);}}>+2</button></>)
}
点击第一次后:
点击第二次后:
发现两次点击都只加了1
在第一次渲染期间,
number
是0,调用onClick:
第一个setNumber(number + 1)
等价于setNumber(0 + 1)
第二个setNumber(number + 1)
等价于setNumber(0 + 1)
React 准备在下一次渲染时更改number
为1,也就是说设置状态为1了两次
在处理状态更新之前,React 会等到事件处理程序中的所有代码都已运行。这使您可以更新多个状态变量——甚至来自多个组件——而不会触发太多重新渲染
q: 在下一次渲染之前需要多次更新相同的state?
答: 可以传递一个函数,该函数根据前一个状态计算下一个状态队列
例如将上述例子替换为setNumber(n => n + 1);
n => n + 1
更新函数(必须是纯函数并且只返回结果)
当您将它传递给状态设置器时:
- 在事件处理程序中的所有其他代码运行之后,React 将此函数排队等待处理。
- 在下一次渲染期间,React 遍历队列并为您提供最终的更新状态。
队列更新 | n | returns |
---|---|---|
n => n + 1 | 0 | 0+1=1 |
n => n + 1 | 1 | 1+1=2 |
状态队列例子
实现状态队列:https://codesandbox.io/s/0xym3z?file=/processQueue.js&utm_medium=sandpack
<button onClick={() => {setNumber(number + 5);setNumber(n => n + 1);setNumber(42);
}}>
队列更新 | n | returns |
---|---|---|
“replace with 5” | 0(unused) | 5 |
n => n + 1 | 5 | 5+1=6 |
“replace with 42” | 6(unused) | 42 |
在下一次渲染期间,React 遍历状态队列:
- setNumber(number + 5): number是0,所以setNumber(0 + 5)。React 将“替换为5”添加到它的队列中。
- setNumber(n => n + 1): n => n + 1是一个更新函数。React 将该函数添加到它的队列中。
- setNumber(42): React 将“替换为42”添加到它的队列中。
5. 更新state中的对象
用 Immer 编写简洁的更新对象state逻辑:Immer编写简洁的更新state逻辑