前言
React采用组件化、声明式编码,通过将不同的组件组合成复杂的UI界面。React 应用程序的组成部分:元素和组件。元素是构成 React 应用的最小砖块。组件是由元素构成的。使用 React 构建的应用只有一个根 DOM 节点。React 将以 小写字母 开头的组件视为原生 DOM 标签,不是原生的标签,组件名称必须以大写字母开头。
生命周期就是指一个对象的生老病死。每个组件都包含 “生命周期方法”,React会在特定的阶段执行这些方法。生命周期方法也被称为 “生命周期回调函数” 、“生命周期钩子函数” 、“生命周期函数” 或“生命周期钩子”。
组件第一次被渲染到 DOM 中的时,被称为“挂载(mount)”。当组件的 props 或 state 发生变化时会触发更新。React DOM 会将元素和它的子元素与它们之前的状态进行比较,通过React的Diffing 算法来减少重复渲染的次数。组件被删除的时候,称为“卸载(unmount)”。
一、生命周期
1.1 生命周期图谱
1.2 生命周期方法
1.2.1 挂载
-
constructor(props)------构造器 执行了1次
在 React 中,构造函数仅用于以下两种情况:
- 通过给 this.state 赋值对象来初始化内部 state
- 为事件处理函数绑定实例
在事件系统中被执行时,是直接调用,会出现 this 没指向类组件实例的情况undefined,此时可以在构造函数中绑定 this。bind 生成新的函数,这里 this 这里是 MyComponent实例对象
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { isHot: true, wind: '微风' }
this.change = this.change.bind(this)
//console.log(this.props);
}
render () {
console.log(this)
const { isHot, wind } = this.state
return <h2 onClick={this.change}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h2>
}
change () {
const changeHot = this.state.isHot
this.setState({
isHot: !changeHot
})
}
}
//渲染组件
ReactDOM.render(<MyComponent />, document.getElementById('test'))
注意:
- 如果不初始化 state 或不进行方法绑定,构造器可省略。
- 有构造器,则一定要super(props);
-
static getDerivedStateFromProps(props, state)
常用于组件初始化或更新时将 props 映射到 state
注意:
- 不能将他定义给实例用,将会被忽略。请将他定义成静态方法
- 必须要有返回值,返回一个状态对象更新 state,返回
null
则不更新任何内容- 此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props
- 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用
-
render()------ 组件初始化和页面 state 或 props 发生变化时执行 (1+n次)
render函数里面可以编写JSX,转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM。会根据props,state值构建并返回一个新的对象,若状态更新则会继续调用。
- createElement
- cloneElement
- 遍历 children
-
componentDidMount()------挂载阶段已完成
只调一次,组件一挂载到页面,就调用
1.2.2 更新
-
static getDerivedStateFromProps(props, state)
-
shouldComponentUpdate(nextProps,nextState) ------控制组件更新的阀门
根据返回值判断是否跳过更新,在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
-
render()
-
getSnapshotBeforeUpdate(preProps, preState)------将要更新前,相当于快照
- 可以返回任何类型的,并将其返回值传递给给componentDidUpdate的snapshot
- 一般用于处理滚动位置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>getSnapshotBeforeUpdate</title>
<style>
#list {
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto;
}
.news {
height: 30px;
}
</style>
</head>
<body>
<div id="test"></div>
<script src="../js/17.0.1/react.development.js"></script>
<script src="../js/17.0.1/react-dom.development.js"></script>
<script src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel">
//scrollTop 向上,不累计
//scrollHeight 30*8=240
class NewsList extends React.Component {
state = { newArr: [] }
componentDidMount () {
this.timer = setInterval(() => {
const { newArr } = this.state
//模拟一条新闻
const news = '新闻' + (newArr.length + 1)
//更新状态
this.setState({
newArr: [news, ...newArr]
})
}, 1000)
}
getSnapshotBeforeUpdate () {
if (this.state.newArr.length >= 20) {
clearInterval(this.timer)
}
return this.refs.newlist.scrollHeight
}
componentDidUpdate (preProps, preState, height) {
this.refs.newlist.scrollTop += this.refs.newlist.scrollHeight - height
}
render () {
return (
<div id="list" ref="newlist">
{
this.state.newArr.map((n, index) => {
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewsList />, document.getElementById('test'))
</script>
</body>
</html>
-
componentDidUpdate(preProps, preState,snapshot)------更新完成
会在更新后会被立即调用。首次渲染不会执行此方法。
注意:
- 如果组件实现了
getSnapshotBeforeUpdate()
生命周期(不常用),则它的返回值将作为componentDidUpdate()
的第三个参数 “snapshot” 参数传递.- 如果shoundComponentUpdate返回值为 false,则不会调用
componentDidUpdate()
。
1.2.3 卸载
-
componentWillUnmount()------将要卸载
主要用于清除 timer,取消网络请求或清除创建的订阅等。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>生命周期</title>
</head>
<body>
<div id="test"></div>
<script src="../js/16.8.4/react.development.js"></script>
<script src="../js/16.8.4/react-dom.development.js"></script>
<script src="../js/16.8.4/babel.min.js"></script>
<script type="text/babel">
class Life extends React.Component {
state = { opacity: 1 }
clean = () => {
//卸载组件
ReactDOM.unmountComponentAtNode(
document.getElementById('test')
)
//can't perform a React state update on an unmounted component. 不能在已经卸载的组件内执行react里的状态的更新
//This is a no-op, but it indicates a memory leak in your application.
//To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
}
//render 1+n次调用
//初始化渲染、状态更新之后
render() {
console.log('render')
return (
<div>
<h3 style={{ opacity: this.state.opacity }}>
生命周期
</h3>
<button onClick={this.clean}>点击</button>
</div>
)
}
//只调一次,组件一挂载到页面,就调用
//组件完成挂载后
componentDidMount() {
this.timer = setInterval(() => {
let { opacity } = this.state
opacity -= 0.1
if (opacity <= 0) {
opacity = 1
}
//设置新的透明度
this.setState({ opacity })
}, 200)
}
//马上要卸载前,组件将要被卸载
componentWillUnmount() {
//清除定时器
clearInterval(this.timer)
}
}
ReactDOM.render(<Life />, document.getElementById('test'))
</script>
</body>
</html>
即将废弃的钩子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
16.4之后需要加上UNSAFE_前缀才能使用
二、Diffing算法
React是基于虚拟 DOM的。通过使用虚拟DOM,完成组件的渲染和更新。当状态中的数据发生变化时,react会根据 新数据 生成 新的虚拟DOM。随后React进行新旧虚拟DOM 的diff比较,进行局部更新。
2.1 react/vue中的key有什么作用?
key是虚拟DOM对象的标识,用于显示于更新。
2.2 Diffing算法
1. 旧虚拟DOM中找到了与新虚拟DOM相同的key
- 若虚拟DOM中内容没变, 直接使用之前的真实DOM
- 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
2. 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到到页面
2.3 为什么遍历列表时,key最好不要用index?
用index作为key可能会引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生重复的真实DOM更新,效率低。
- 如果结构中还包含输入类的DOM,会产生错误DOM更新
所以使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。