一、生命周期的三个阶段(旧)
1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
1.constructor()
2.componentWillMount()
3.render() —>必用,初始化渲染调用
4.componentDidMount() —>“常用”,一般做一些初始化的事情,例如:开启定时器,发送ajax请求,订阅消息。
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1.shouldComponentUpdate()
2.componentWillUpdate()
3.render() —>必用,更新渲染调用
4.componentDidUpdate() —>“常用”,一般做一些收尾的事情,例如:关闭定时器,取消订阅消息。
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1.componentWillUnmount()
挂载时:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期函数</title>
</head>
<body>
<div id="test"></div>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 创建组件
class Count extends React.Component{
constructor(){
super();
this.state = {count:0};
console.log('constructor');
}
add = () => {
let {count} = this.state;
this.setState({
count:count+1
})
}
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
}
force = () =>{
this.forceUpdate();
}
// 组件将要挂载的钩子
componentWillMount(){
console.log('componentWillMount');
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log('componentDidMount');
}
// 组件将要卸载的钩子
componentWillUnmount(){
console.log('componentWillUnmount');
}
// 控制组件更新的阀门
shouldComponentUpdate(){
console.log('shouldComponentUpdate');
return false;
}
// 组件将要更新钩子
componentWillUpdate(){
console.log('componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('componentDidUpdate');
}
render(){
console.log('render');
let {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<Count/>,document.getElementById('test'));
</script>
</body>
</html>
父组件render:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期函数</title>
</head>
<body>
<div id="test"></div>
<script src="../js/react.development.js"></script>
<script src="../js/react-dom.development.js"></script>
<script src="../js/babel.min.js"></script>
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
// 创建组件
class A extends React.Component{
state = {carName:'奔驰'};
changeCar = () =>[
this.setState({
carName:'宝马'
})
]
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
class B extends React.Component{
// 组件将要接收新的props钩子
componentWillReceiveProps(props){
console.log('B----componentWillReceiveProps',props);
}
// 控制组件更新的阀门
shouldComponentUpdate(){
console.log('shouldComponentUpdate');
return true;
}
// 组件将要更新钩子
componentWillUpdate(){
console.log('componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('componentDidUpdate');
}
render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
// 渲染组件
ReactDOM.render(<A/>,document.getElementById('test'));
</script>
</body>
</html>
二、生命周期流程图(新)
1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
1.constructor()
2.getDerivedStateFromProps
3.render() —>必用
4.componentDidMount() —>“常用”,一般做一些初始化的事情,例如:开启定时器,发送ajax请求,订阅消息。
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1.getDerivedStateFromProps
2.shouldComponentUpdate()
3.render() —>必用
4.getSnapshotBeforeUpdate
4.componentDidUpdate() —>“常用”,一般做一些收尾的事情,例如:关闭定时器,取消订阅消息。
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1.componentWillUnmount()
新生命周期函数演示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期函数</title>
</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/javascript" src="../js/17.0.1/prop-types.js"></script>
<script type="text/babel">
// 创建组件
class Count extends React.Component{
constructor(){
super();
this.state = {count:0};
console.log('constructor');
}
add = () => {
let {count} = this.state;
this.setState({
count:count+1
})
}
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
}
force = () =>{
// this.forceUpdate();
}
// 若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDeriveStateFromProps',props,state);
// return props;
return null;
}
// 在更新之前获取快照
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 1111; // 1111-->传给了componentDidUpdate的第三个参数
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log('componentDidMount');
}
// 组件将要卸载的钩子
componentWillUnmount(){
console.log('componentWillUnmount');
}
// 控制组件更新的阀门
shouldComponentUpdate(){
console.log('shouldComponentUpdate');
return true;
}
// 组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotvalue){
console.log('componentDidUpdate',preProps,preState,snapshotvalue);
}
render(){
console.log('render');
let {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<Count count={199}/>,document.getElementById('test'));
</script>
</body>
</html>
getSnapshotBeforeUpdate案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期函数</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/javascript" src="../js/17.0.1/prop-types.js"></script>
<script type="text/babel">
// 创建组件
class NewList extends React.Component{
state = {newsArr:[]};
componentDidMount(){
setInterval(()=>{
const {newsArr} = this.state;
const news = '新闻'+(newsArr.length+1);
this.setState({
newsArr:[news,...newsArr]
})
},1000)
}
getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight;
}
componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop += this.refs.list.scrollHeight - height;
}
render(){
return(
<div className="list" ref="list">
{this.state.newsArr.map((item,index)=>{
return <div className="news" key={index}>{item}</div>
})}
</div>
)
}
}
// 渲染组件
ReactDOM.render(<NewList/>,document.getElementById('test'));
</script>
</body>
</html>
【面试提问】react新旧生命周期的对比?
react新生命周期即将废弃的勾子:
- 1.componentWillMount
- 2.componentWillReceiveProps
- 3.componentWillUpdate
原因:现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
react新生命周期新增加的钩子:
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
三、DOM的diffing算法
基本原理图:
经典面试题:
- 1). react/vue中的key有什么作用?(key的内部原理是什么?)
- 2). 为什么遍历列表时,key最好不要用index?
原因如下:
1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2). 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3). 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1). 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2). 如果确定只是简单的展示数据,用index也是可以的。
代码演示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期函数</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/javascript" src="../js/17.0.1/prop-types.js"></script>
<script type="text/babel">
/*
慢动作回放----使用index索引值作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
-----------------------------------------------------------------
慢动作回放----使用id唯一标识作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
*/
// 创建组件
class Person extends React.Component{
state = {
persons:[
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
]
}
add=()=>{
const {persons} = this.state;
const p = {id:persons.length+1,name:'小王',age:20}
this.setState({
persons:[p,...persons]
})
}
render(){
console.log(this.state.persons);
return(
<div>
<h1>展示人员信息</h1>
<button onClick={this.add}>添加一个小王</button>
<ul>
{this.state.persons.map((item)=>{
return <li key={item.id}>{item.name}---{item.age}</li>
})}
</ul>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<Person/>,document.getElementById('test'));
</script>
</body>
</html>