1. react合成事件 SyntheticEvent
(React 17和老版本對(duì)比)
React 17 以前
React在原生的DOM事件上封裝了一層吱韭,稱為SyntheticEvent(合成事件)。所有事件都會(huì)冒泡到根節(jié)點(diǎn) - document
上,然后進(jìn)行后續(xù)處理哎榴。
- 優(yōu)勢(shì)1:兼容各類瀏覽器的Dom事件
- 優(yōu)勢(shì)2:事件池怠惶。
事件池中裝滿了SyntheticEvent對(duì)象宰啦,需要時(shí)池中取出使用,用完后再放回池中氓栈,這就意味著 SyntheticEvent對(duì)象可以被緩存且反復(fù)使用。目的是提高性能婿着,減少創(chuàng)建不必要的對(duì)象授瘦。
當(dāng)調(diào)用事件回調(diào)函數(shù)之后,合成對(duì)象上的所有屬性重置為null竟宋,但是合成事件對(duì)象依舊存在提完。
因此,寫React事件回調(diào)函數(shù)的時(shí)候不能將 event 用于異步操作 —— 當(dāng)異步操作真正執(zhí)行的時(shí)候丘侠,SyntheticEvent對(duì)象有可能已經(jīng)被重置了徒欣。
import React, { Component } from "react";
class TextInput extends Component {
state = {
counter: 0,
value: this.props.defaultValue,
}
// 由于 setState 是異步操作,event.target.value 在運(yùn)行時(shí)可能已經(jīng)被重置了
handleChange = event =>
this.setState(prevState => ({ value: event.target.value, counter: prevState.counter + 1 }));
render() {
return (
<span>Edited {this.state.editionCounter} times</span>
<input
type="text"
value={this.state.value}
onChange={this.handleChange} // WRONG!
/>
)
}
}
一般有兩種解法:
- 使用
event.persist()
持久化合成事件:將當(dāng)前event挪出事件池蜗字,從而該event屬性值可以一直存在而不會(huì)被重置打肝。- 缺點(diǎn):放棄了
SyntheticEvent
事件復(fù)用能力,不推薦
- 缺點(diǎn):放棄了
- 緩存所需的event屬性值
- 缺點(diǎn):代碼略啰嗦挪捕。但是粗梭,推薦
import React, { Component } from "react";
class TextInput extends Component {
state = {
counter: 0,
value: this.props.defaultValue,
}
handleChange = event => {
const value = event.target.value; // value這個(gè)本地變量已經(jīng)保存了目標(biāo)值
this.setState(prevState => ({ value, editionCounter: prevState.counter + 1 }));
}
render() {
return (
<span>Edited {this.state.editionCounter} times</span>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
)
}
}
如要想要阻止冒泡,不要直接調(diào)用原生e.stopPropagation()
方法级零,沒用的断医。
利用合成事件上的nativeEvent
訪問(wèn)到原生事件,再調(diào)用nativeEvent.stopImmediatePropagation
方法。
https://developer.mozilla.org/zh-CN/docs/Web/API/Event/stopImmediatePropagation
React 17
變化1:事件綁定將從document
下移到root
節(jié)點(diǎn)孩锡,
變化2:Web 端的 React 17 不使用事件池(https://zh-hans.reactjs.org/docs/legacy-event-pooling.html)酷宵。由于 SyntheticEvent
不再放入事件池,e.persist()
將不再生效躬窜。
為什tReact 17 廢棄了事件復(fù)用機(jī)制呢浇垦?因?yàn)樵诂F(xiàn)代瀏覽器下這種性能優(yōu)化沒有意義,反而給開發(fā)者帶來(lái)了困擾
2. 加了key
就一定好嗎荣挨?(此知識(shí)點(diǎn)React和Vue通用)
渲染列表時(shí)男韧,大家都知道要加key
值,為什么呢默垄?為了配合diff
算法做性能優(yōu)化呀此虑?
那么如果是純文字改動(dòng)呢?
<ul>
<li id="1">1</li>
<li id="2">2</li>
<li id="3">3</li>
<li id="4">4</li>
</ul>
// 更新為
<ul>
<li id="1">1</li>
<li id="2">3</li>
<li id="3">4</li>
<li id="4">5</li>
</ul>
如果指定了key
口锭,需要做移動(dòng)朦前,刪除,新增鹃操,三種操作韭寸。但是,我們僅僅更新個(gè)textContent
荆隘,需要這么大動(dòng)作嗎恩伺?
那就不要指定key
好了,React(或者Vue)會(huì)復(fù)用原DOM節(jié)點(diǎn)椰拒,只做textContent
更新而已晶渠,性能明顯更好。
當(dāng)然燃观,如果節(jié)點(diǎn)上綁定了事件褒脯,且事件和節(jié)點(diǎn)內(nèi)容相關(guān),那么請(qǐng)務(wù)必小心缆毁,為了不必要的Bug番川,還是建議加上key
。
3. 不要直接將prop
賦值給state
場(chǎng)景:子組件用props初始化state數(shù)據(jù)积锅。
3.1 先看看class模式的案例代碼:
class Child extends React.Component {
constructor(props) {
super(props)
this.state = {
list: props.list
}
}
render() {
return (
<div>
{this.state.list.map((item, index) => {
return <h1 key={index}>Hello, {item.name}</h1>
})}
</div>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [{ name: 'lily' }, { name: 'bob' }]
}
}
handleCilck = () => {
this.setState({
list: this.state.list.concat([{ name: 'parent' }])
})
}
render() {
return (
<div>
<button onClick={this.handleCilck}>父組件更新state</button>
<Child list={this.state.list} />;
</div>
)
}
}
點(diǎn)擊父組件更新state
按鈕爽彤,子組件的列表會(huì)更新嗎?
當(dāng)然是No缚陷。因?yàn)樽咏M件的constructor
只會(huì)在初始化時(shí)運(yùn)行适篙,所以更新階段是不會(huì)對(duì)state
重新復(fù)制。
解決方案也比較簡(jiǎn)單箫爷,利用componentWillReceiveProps
鉤子函數(shù)觸發(fā)子組件state
更新嚷节。
componentWillReceiveProps(props) {
this.setState({
list: props.list
})
}
3.2 useState
開發(fā)模式
function Component (props) => {
const [name, setName] = useState(props.name)
console.log(name)
}
同樣的聂儒,如果props更新,子組件依舊用第一次的值來(lái)渲染硫痰。
可以利用uesEffect
解決這個(gè)問(wèn)題:
function Component (props) => {
const [name, setName] = useState(props.name)
useEffect(() => {
setName(props.name)
}, [props.name])
}
4. 如何在不添加依賴的情況下也能獲取到最新值呢衩婚?(Hook)
答案:利用setXXX
函數(shù)回調(diào)模式。
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // 劃重點(diǎn)
}, 1000);
return () => clearInterval(id);
}, []);
一個(gè)依賴參數(shù)好解決效斑,如果多個(gè)依賴參數(shù)怎么辦非春?
答案:用useReducer
吧。
import React, { useReducer, useEffect } from "react";
const initialState = {
count: 0,
step: 1,
};
const reducer = (state, action) {
const { count, step } = state;
if (action.type === 'tick') {
return { count: count + step, step };
} else {
throw new Error();
}
}
// 計(jì)數(shù)器組件
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
return (
<h1>{count}</h1>
);
}
5. 什么時(shí)候應(yīng)該用useEffect
缓屠?
React組件有兩個(gè)重要的概念:
- Rendering code 渲染代碼
- Event handlers 事件處理器奇昙,其包括
- 更新
input
等輸入框 - 提交表格
- 導(dǎo)航到其它頁(yè)面
- 更新
看下例子:
function App() {
const [name, update] = useState('KaSong');
// Event handlers
const changeName = () => {
update('hello world');
}
// Rendering code + Event handlers
return <div onClick={changeName}>Hello {name}</div>;
}
一個(gè)純組件,僅僅只包含Rendering code
敌完。
而Event handlers
是「組件內(nèi)部包含的函數(shù)」储耐,用于執(zhí)行用戶操作。
目前為止滨溉,組件都沒有用到useEffect
什湘。
那什么樣的行為,必須用useEffect
處理呢晦攒?
在視圖渲染后觸發(fā)的副作用闽撤,就屬于effect,應(yīng)該交給useEffect
處理勤家!
比如腹尖,常見操作柳恐,根據(jù)接口獲取數(shù)據(jù)后渲染頁(yè)面伐脖。這種行為并不是組件主動(dòng)發(fā)起的行為,而是一種被動(dòng)行為乐设。