添加交互性
- 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逻辑