目录
七、setState详细解析和React性能优化
1、setState异步更新
import React from "react";
function Home(props) {
return <h1>{props.message}</h1>
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '黄婷婷'
}
}
render() {
return (<div>
<h2>{this.state.message}</h2>
<button onClick={e => this.changeMessage()}>按钮</button>
<Home message={this.state.message}/>
</div>)
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('生命周期', this.state.message)
}
changeMessage() {
/**
* 1、setState是父类方法
* 2、setState是异步更新
* * setState设计为异步,可以显著的提升性能
* - 如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,
* 界面重新渲染,这样效率是很低的
* - 最好的办法应该是获取到多个更新,之后进行批量更新
* * 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步
* - state和props不能保持一致性,会在开发中产生很多的问题
* 3、获取异步更新后的数据
* - setState(更新的state, 回调函数)
* - componentDidUpdate生命周期函数(先于回调函数执行)
*/
this.setState({
message: '孟美岐'
}, () => {
console.log('回调函数', this.state.message)
})
console.log('异步更新', this.state.message)
}
}
2、setState同步更新
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '黄婷婷'
}
}
render() {
return (<div>
<h2>{this.state.message}</h2>
<button onClick={e => this.changeMessage()}>按钮1</button>
<button id='btn'>按钮2</button>
</div>)
}
componentDidMount() {
const btn = document.getElementById('btn');
// 2、将setState放入到原生DOM事件中(测试失败)
btn.addEventListener('click', () => {
this.setState({
message: '孟美岐'
})
console.log('同步更新', this.state.message)
})
}
/**
* 1、setState一定是异步吗?
* - 在组件生命周期或React合成事件中,setState是异步
* - 在setTimeout或者原生dom事件中,setState是同步
*/
changeMessage() {
// 1、将setState放入到定时器中(测试失败)
setTimeout(() => {
this.setState({
message: '姜贞羽'
})
console.log('同步更新', this.state.message)
}, 0)
}
}
3、setState数据的合并
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '黄婷婷',
name: '孟美岐'
}
}
render() {
return (<div>
<h2>{this.state.message}</h2>
<h2>{this.state.name}</h2>
<button onClick={e => this.changeMessage()}>按钮</button>
</div>)
}
changeMessage() {
// 1、数据合并原理:Object.assign({}, this.state, {message: '姜贞羽'})
this.setState({
message: '姜贞羽'
})
}
}
4、setState本身的合并
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (<div>
<h2>{this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
</div>)
}
increment() {
// 1、setState本身被合并
/*
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 1
})*/
// 2、setState合并时进行累加
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
})
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
})
this.setState((prevState, props) => {
return {
counter: prevState.counter + 1
}
})
}
}
5、React更新机制
* React的渲染流程
- jsx -> 虚拟dom -> 真实dom
* React的更新流程
- props/state改变 -> render函数重新执行 -> 产生新的dom树
-> 新旧dom树进行diff -> 计算出差异进行更新 -> 更新到真实的dom
* diff算法
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定
6、列表中keys的作用
import React from "react";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: ['射雕英雄传', '神雕侠侣']
}
}
/**
* 一、对比不同类型的元素
* - 当节点为不同的元素,React会拆卸原有的树,并且建立起新的树
* - 当卸载一棵树时,对应的dom节点也会被销毁,组件实例将执行componentWillUnmount()方法
* - 当建立一棵新的树时,对应的dom节点会被创建以及插入到dom中,组件实例将执行
* componentWillMount()方法,紧接着componentDidMount()方法
* 二、对比同一类型的元素
* - 当比对两个相同类型的React元素时,React会保留dom节点,仅比对及更新有改变的属性
* - 组件会保持不变,React会更新该组件的props,并且调用componentWillReceiveProps()
* 和componentWillUpdate()方法
* - 下一步,调用render()方法,diff算法将在之前的结果以及新的结果中进行递归
* 三、对子节点进行递归
* - 在默认条件下,当递归dom节点的子元素时,React会同时遍历两个子元素的列表;当产生差异时,
* 生成一个mutation
* 四、keys的优化
* - 当子元素拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素
* - key相同的子元素仅仅进行位移,不需要进行任何的修改
* - key不同的子元素则进行挂载或卸载操作
* 五、key的注意事项
* - key应该是唯一的
* - key不要使用随机数
* - 使用index作为key,对性能是没有优化的
*/
render() {
return (<div>
<ul>
{this.state.movies.map(item => (<li key={item}>{item}</li>))}
</ul>
<button onClick={e => this.insertMovie()}>按钮</button>
</div>)
}
insertMovie() {
this.setState({
movies: ['倚天屠龙记', ...this.state.movies]
})
}
}
7、render函数被调用
import React, {memo} from "react";
class Header extends React.PureComponent {
render() {
console.log('Header的render')
return (<h3>{this.props.counter}</h3>)
}
}
class Bodyer extends React.Component {
render() {
console.log('Bodyer的render')
return (<div>
<MemoLefter/>
<Righter/>
</div>)
}
}
/**
* 4、memo参数传入函数式组件,state/props不发生改变则不执行render(推荐)
*/
const MemoLefter = memo(function Lefter() {
console.log('Lefter的render')
return (<h3>左边</h3>)
})
function Righter() {
console.log('Righter的render')
return (<h3>右边</h3>)
}
/**
* 3、类组件继承PureComponent,state/props不发生改变则不执行render(推荐)
* - 会在原型上增加一个属性(isPureReactComponent = true)
*/
class Footer extends React.PureComponent {
render() {
console.log('Footer的render')
return (<h3>尾部</h3>)
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
/**
* 1、render函数被调用
* - 当调用App的render函数,所有子组件的render函数都会被重新调用
* - 事实上很多组件没必要重新render,调用render应该有一个前提(state/props发生改变)
*/
render() {
console.log('App的render')
return (<div>
<button onClick={e => this.increment()}>按钮</button>
<Header counter={this.state.counter}/>
<Bodyer/>
<Footer/>
</div>)
}
/**
* 2、生命周期(简称SCU)
* - nextProps:修改后的props;nextState:修改后的state
* - 返回true:会调用render方法;返回false:不会调用render方法;默认返回true
*/
shouldComponentUpdate(nextProps, nextState, nextContext) {
return true
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
八、知识补充和受控非受控组件
1、setState传递的数据需要是不可变的数据
import React from "react";
export default class App extends React.PureComponent {
constructor(props) {
super(props);
// 引用类型
this.state = {
friends: [{
name: '敌法师',
age: 18
}, {
name: '赏金猎人',
age: 19
}, {
name: '力丸',
age: 20
}]
}
}
render() {
return (<div>
<h2>好友列表</h2>
<ul>
{this.state.friends.map((item, index) => {
return (<li key={item.name}>
姓名:{item.name}-
年龄:{item.age}-
<button onClick={e => this.incrementAge(index)}>age+1</button>
</li>)
})}
</ul>
<button onClick={e => this.insertData()}>添加数据</button>
</div>)
}
/*
shouldComponentUpdate(nextProps, nextState, nextContext) {
if (this.state.friends !== nextState.friends) {
return true
}
return false
}*/
insertData() {
const newData = {name: '莱恩', age: 21}
// 1、在开发中不要这样做
/*
this.state.friends.push(newData)
this.setState({
friends: this.state.friends
})*/
// 2、推荐做法
const newFriends = [...this.state.friends]
newFriends.push(newData)
this.setState({
friends: newFriends
})
}
incrementAge(index) {
const newFriends = [...this.state.friends]
newFriends[index].age++
this.setState({
friends: newFriends
})
}
}
2、全局事件传递events
import React from "react";
import {EventEmitter} from "events"
// 事件总线:event bus
const eventBus = new EventEmitter();
class Home extends React.PureComponent {
componentDidMount() {
eventBus.addListener('sayHello', this.handleSayHelloListener)
}
componentWillUnmount() {
eventBus.removeListener('sayHello', this.handleSayHelloListener)
}
handleSayHelloListener(...args) {
console.log(args)
}
render() {
return (<div>
Home
</div>)
}
}
class Profile extends React.PureComponent {
render() {
return (<div>
Profile
<button onClick={e => this.emitEvent()}>点击了profile按钮</button>
</div>)
}
emitEvent() {
eventBus.emit('sayHello', 'hello home', 123)
}
}
export default class App extends React.PureComponent {
render() {
return (<div>
<Home/>
<Profile/>
</div>)
}
}
3、如何使用ref
import React from "react";
/**
* 1、在React的开发模式中,通常情况下不需要、也不建议直接操作dom原生,但是某些特殊的情况,
* 确实需要获取到dom进行某些操作
* - 管理焦点,文本选择或媒体播放
* - 触发强制动画
* - 集成第三方dom库
* 2、如何创建refs来获取对应的dom呢?目前有三种方式
* 3、ref的值根据节点的类型而有所不同
* - 当ref属性用于html元素时,构造函数中使用React.createRef()
* 创建的ref接收底层dom元素作为其current属性
* - 当ref属性用于自定义class组件时,ref对象接收组件的挂载实例作为其current属性
* - 你不能在函数组件上使用ref属性,因为他们没有实例
* 4、函数式组件是没有实例的,所以无法通过ref获取他们的实例
* - 但是某些时候,我们可能想要获取函数式组件中的某个dom元素
* - 这个时候我们可以通过React.forwardRef,后面我们也会学习hooks中如何使用ref
*/
export default class App extends React.PureComponent {
constructor(props) {
super(props);
this.titleRef = React.createRef()
this.counterRef = React.createRef()
this.titleEl = null
}
render() {
return (<div>
{/*<h2 ref=字符串/对象/函数>hello react</h2>*/}
<h2 ref="titleRef">hello react</h2>
{/*目前React推荐的方式*/}
<h2 ref={this.titleRef}>hello react</h2>
<h2 ref={arg => this.titleEl = arg}>hello react</h2>
<button onClick={e => this.changeText()}>改变文本</button>
<hr/>
<Counter ref={this.counterRef}/>
<button onClick={e => this.appBtnClick()}>App按钮</button>
</div>)
}
appBtnClick() {
this.counterRef.current.increment()
}
changeText() {
// 1、使用方式一:字符串(不推荐,后续的更新会删除)
this.refs.titleRef.innerHTML = "hello coderwhy"
// 2、使用方式二:对象方式
this.titleRef.current.innerHTML = "hello javascript"
// 3、使用方式三:回调函数方式
this.titleEl.innerHTML = "hello typescript"
}
}
class Counter extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (<div>
<h2>当前计数:{this.state.counter}</h2>
<button onClick={e => this.increment()}>+1</button>
</div>)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
4、认识受控组件
import React from "react";
export default class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
username: ""
}
}
render() {
return (<div>
<form onSubmit={e => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
{/*受控组件*/}
<input
id="username"
type="text"
onChange={e => this.handleChange(e)}
value={this.state.username}/>
</label>
<input type="submit" value="提交"/>
</form>
</div>)
}
handleSubmit(event) {
event.preventDefault()
console.log(this.state.username)
}
handleChange(event) {
this.setState({
username: event.target.value
})
}
}
Element | Value property | Change callback | New value in the callback |
---|---|---|---|
<input type="text" /> | value="string" | onChange | event.target.value |
<input type="checkbox" /> | checked= | onChange | event.target.checked |
<input type="radio" /> | checked= | onChange | event.target.checked |
<textarea /> | value="string" | onChange | event.target.value |
<select /> | value="option value" | onChange | event.target.value |
import React from "react";
export default class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
fruits: "banana"
}
}
render() {
return (<div>
<form onSubmit={e => this.handleSubmit(e)}>
<select
name="fruits"
onChange={e => this.handleChange(e)}
value={this.state.fruits}>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橘子</option>
</select>
<input type="submit" value="提交"/>
</form>
</div>)
}
handleSubmit(event) {
event.preventDefault()
console.log(this.state.fruits)
}
handleChange(event) {
this.setState({
fruits: event.target.value
})
}
}
5、非受控组件
import React from "react";
export default class App extends React.PureComponent {
constructor(props) {
super(props);
this.usernameRef = React.createRef()
}
render() {
return (<div>
<form onSubmit={e => this.handleSubmit(e)}>
<label htmlFor="username">
用户:<input id="username" type="text" ref={this.usernameRef}/>
</label>
<input type="submit" value="提交"/>
</form>
</div>)
}
handleSubmit(event) {
event.preventDefault()
console.log(this.usernameRef.current.value)
}
}