React 高级内容(2)

React中ref的使用(尽量不用)

ref->reference 引用
在React中我们可以使用ref来直接完成DOM的引用

但是尽量不要使用ref来获取页面上的DOM,要时刻记得我们要减少去操作DOM,我们在React中应该直接去操作state数据,十分复杂的情况下可使用(比如动画)。

有时候同时使用ref和setState时数据的获取会有延迟,是因为setState是异步的,他并不是马上处理的,所以会停留在上一次的数据。此时可以 将获取DOM的这段代码放在setState的第二个参数的函数里,那么她就会在页面完全渲染好以后再进行DOM元素的获取

使用e.target引用对应的DOM元素

TodoList中:

1
2
3
4
5
6
<input
id="inserArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>

TodoList中
我们可以通过e.target来获取元素对应的DOM,也可以通过ref来获取元素对应的DOM。

使用ref引用对应的DOM元素

  • 在react16的语法中,ref等于一个函数,这个函数自动接收一个参数(即对应的DOM元素),参数名可以自定义
  • ref={(input) => { this.input = input }}
    • 我们给获取到的对应DOM元素取名input,将它赋值给this.input,则接下来在组件中可以通过this.input来操作这个DOM元素

可改写 TodoList,render函数中:

1
2
3
4
5
6
7
8
<input
id="inserArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
//`this.input` 指input对应的DOM元素
ref={(refInput) => { this.input = refInput }}
/>

同时修改TodoList中的handleInputChange:

1
2
3
4
5
6
7
handleInputChange() {
// this.input相当于e.target
const value = this.input.value;
this.setState(() => ({
inputValue: value
}));
}

效果和上面使用e.target是一样的,但其实并不推荐使用ref。【原代码改回e.target再继续下一节课的学习】

同时使用ref和setState时的问题与解决

有时候同时使用ref和setState时数据的获取会有延迟,是因为setState是异步的,他并不是马上处理的,所以会停留在上一次的数据。此时可以 将获取DOM的这段代码放在setState的第二个参数的函数里,那么她就会在页面完全渲染好以后再进行DOM元素的获取

例子

在TodoList的render中,我们<ul>增加一个ref去获取对应的DOM节点(也就是ul元素):

1
2
3
4
//this.ul指向对应的ul元素,refUl是自定义的
<ul ref={(refUl) => { this.ul = refUl }}>
{this.getTodoItem()}
</ul>

然后在点击“提交”按钮的绑定事件函数 handleBtnClick 中增加一个打印语句,打印ul元素下的所有div标签的长度

1
2
3
4
5
6
7
8
9
10
handleBtnClick() {
// prevState:修改数据前的那一次的数据,相当于this.state,但更靠谱
this.setState((prevState) => ({
/* 通过扩展运算符将原本的list与输入框中得到的inputValue合并数组后赋给list */
list: [...prevState.list, prevState.inputValue],
/* 清空输入框中的值 */
inputValue: ""
}));
console.log(this.ul.querySelectorAll("div").length);
}

handleBtnClick函数主要作用就是把输入框中的内容加到列表中*(state的list中)并清空输入框,按理说每输入一次div标签就会增加一个。那么输入1时长度应该是1,再次输入2时长度应该是2,可结果明显是DOM元素的获取有延迟
结果
这是因为setState是异步的,并不会马上执行,所以获取的数据有所延迟,此时可以 将获取DOM的这段代码放在setState的第二个参数的函数里,那么她就会在页面完全渲染好以后再进行DOM元素的获取

1
2
3
4
5
6
7
8
9
10
11
handleBtnClick() {
// prevState:修改数据前的那一次的数据,相当于this.state,但更靠谱
this.setState((prevState) => ({
/* 通过扩展运算符将原本的list与输入框中得到的inputValue合并数组后赋给list */
list: [...prevState.list, prevState.inputValue],
/* 清空输入框中的值 */
inputValue: ""
}), () => {
console.log(this.ul.querySelectorAll("div").length);
});
}

结果:
结果


React 的 生命周期函数

例子

  • “当props或state发生改变时,组件自动调用render函数进行页面挂载。”那么render函数其实就是生命周期函数
  • “当组件被创建时,会自动调用 constructor函数 (构造函数)。”所以你也可以认为 constructor函数 是生命周期函数,但是 constructor函数 并不是React独有的,他是ES6自带的,所以我们不把 constructor函数 归类为 生命周期函数 ,但实际上没区别。

总结

组件/DOM第一次挂载的流程(mounting)

理解:mount 挂载 -> 将组件放到页面上 -> 渲染页面(第一次)

组件第一次挂载的流程:UNSAFE_componentWillMount函数 -> render函数 ->componentDidMount函数

被React弃用并需要替代的函数们

使用componentWillMount时报出警告:

1
Warning: componentWillMount has been renamed, and is not recommended for use. 

React 宣布 生命周期方法重命名为

  • componentWillMount → UNSAFE_componentWillMount
  • componentWillReceiveProps → UNSAFE_componentWillReceiveProps
  • componentWillUpdate → UNSAFE_componentWillUpdate

UNSAFE_componentWillMount函数

该函数只在页面即将被挂载时执行,也就是只在第一次将组件放(渲染)到页面上之前才会执行,后续在输入框内输入任何数据都不涉及组件挂载,也就不会执行该函数了。

例子
在TodoList.js中添加:
UNSAFE_componentWillMount函数

render函数(进行页面挂载)

当props或state发生改变时,组件自动调用render函数进行页面挂载,将页面渲染出来。

componentDidMount函数

在render函数下方添加componentDidMount函数:

1
2
3
componentDidMount() {
console.log("componentDidMount");
}

结果

【我们在每个生命周期函数内添加了对应的打印语句来观察结果】

可以看到执行顺序是:UNSAFE_componentWillMount函数 -> render函数 ->componentDidMount函数
三者顺序

清空console框,再次输入数据,此时只有render函数被执行。说明只在组件第一次挂载时才执行UNSAFE_componentWillMount函数 和 componentDidMount函数
第一次挂载才执行两个函数


组件更新的流程

更新state数据的流程
shouldComponentUpdate函数 判断是否需要更新,不需要(false)则不再执行下面的函数,需要则顺序执行UNSAFE_componentWillUpdate函数 -> componentDidUpdate函数

更新props数据的流程
比起更新state数据的流程要在最前方多出一个函数:componentWillReceiveProps函数

当一个组件他自己是顶层组件,没有父组件的情况下,他没有接收到props参数,那么就不会执行 函数,比如TodoList中就没有props参数。

shouldComponentUpdate函数

在组件更新之前会被运行,该函数要求返回一个布尔值。
“我的组件需要被更改吗?”返回的布尔值相当于给他一个回答,当返回false时不论你怎么修改state数据,组件都在执行该函数后明白你不需要更新,那么render函数就不会被执行,页面也就不会产生任何的反馈。

注意:shouldComponentUpdate函数其实应该自带两个参数(nextProps,nextState)表示即将更新的state和props,具体可看笔记“React高级内容(3)”中的(使用shouldComponentUpdate做性能优化)

例子
【我们在每个生命周期函数内添加了对应的打印语句来观察结果】

一样在TodoList中做演示:
TodoList
当我们鼠标一点进input框就会执行该函数
true的结果
如果将返回值设为false,则不论你怎么修改state数据,组件都在执行该函数后明白你不需要更新,那么render函数就不会被执行,页面也就不会产生任何的反馈:
false的结果

UNSAFE_componentWillUpdate函数

componentWillUpdate函数被弃用,使用会报警告,应使用UNSAFE_componentWillUpdate函数替代:

1
react-dom.development.js:12449 Warning: componentWillUpdate has been renamed, and is not recommended for use.
  • 组件被更新之前,他会自动执行,但是他在shouldComponentUpdate之后执行
  • 如果shouldComponentUpdate返回true他才执行
  • 如果返回false,这个函数就不会被执行了

在TodoList中添加

1
2
3
UNSAFE_componentWillUpdate() {
console.log("UNSAFE_componentWillUpdate");
}

componentDidUpdate函数

组件更新完成后,他会被执行

在TodoList中添加

1
2
3
componentDidUpdate() {
console.log("componentDidUpdate");
}

结果

【我们在每个生命周期函数内添加了对应的打印语句来观察结果】

shouldComponentUpdate函数返回值为true时的流程:
true

shouldComponentUpdate函数返回值为false时的流程:
false

UNSAFE_componentWillReceiveProps函数

componentWillReceiveProps函数已经被React弃用, 我们需要使用UNSAFE_componentWillReceiveProps函数代替他。

执行条件:一个组件要从父组件接受参数
如果这个组件是第一次存在于父组件中,则该函数不会被执行
如果这个组件之前已经存在于父组件中,该函数就会执行

在TodoList中添加该函数

1
2
3
UNSAFE_componentWillReceiveProps(){
console.log("UNSAFE_componentWillReceiveProps");
}

然而并无反应,因为TodoList就是顶层函数,他没有父组件,也就不会接受props参数,自然也不存在props更新。

在TodoItem中添加该函数

1
2
3
UNSAFE_componentWillReceiveProps(){
console.log("child UNSAFE_componentWillReceiveProps");
}

结果

【我们在每个生命周期函数内添加了对应的打印语句来观察结果】

首先我们要明确,TodoItem这个子组件控制的是输入框下面的ul列表

当我们输入1并提交,第一次往TodoItem中放入数据时,UNSAFE_componentWillReceiveProps函数并没被执行,这是因为 这个组件必须之前已经存在于父组件中,该函数才会执行
第一次放入数据
当我们再次输入2并点击提交时,完美符合执行条件,该函数被执行:
第二次放入数据


把组件从页面去除的流程(unmounting)

当这个组件即将从页面中剔除时,该生命周期函数会被执行

componentWillUnmount函数

在TodoItem中添加该函数

1
2
3
componentWillUnmount() {
console.log("child componentWillUnmount");
}

结果

【我们在每个生命周期函数内添加了对应的打印语句来观察结果】

输入并提交1以后删除无序列表中的“你好呀-1”:
结果1

依次输入并提交1、2,再删除无序列表中的“你好呀-2”:
结果2

结果2的原因:TodoList中放在ul标签中的getTodoItem告诉我们,遍历list列表后数组中的每一项都会返回一个子组件TodoItem,所以该无序列表中的每一项都是一个子组件!
TodoList中


,