React调试工具的安装和使用
现在已经可以写一些简单的React代码了
但经常使用console.log来调成程序显得很傻
React在浏览器端是有一个调试工具,React developer tools
,这是React人比必下的一个调试工具!
React developer tools的三种状态
React developer tools
有三种颜色,三种颜色代表三种状态
- 灰色: 这种就是不可以使用,说明页面不是用React编写的
- 黑色: 说明页面是用React编写的,并且处于线上环境
- 红色: 说明页面使用React编写的,并且处于调试环境当中
React developer tools使用
打开浏览器,F12
进入开发者工具,在面板的最后一个,会有一个React
,这就是安装的插件了
在这里可以清晰地看到React的结构,让自己写的代码更加清晰,还可以看到组件间的数据传递,就不需要用console.log来测试程序了
PropTypes校验传递值
在父组件向子组件传递数据时,使用了属性的方式,也就是props
但“小姐姐服务菜单”的案例并没有任何的限制,这在工作中正式开发是完全不允许的,大型项目,如果不校验,后期会变得异常混乱,业务逻辑也没法保证
PropTypes简单应用
<XiaojiejieItem
index={index}
key={index+item}
deleteItem={this.deleteItem.bind(this)}
content={item} />
Xiaojiejie.js
父组件里向XiaojiejieItem.js
子组件传递了4个值,有字符串,有数字,有方法
这些都可以使用PropTypes
进行限制,首先进行引入
- 父组件传递给子组件,肯定是在子组件里做校验
import PropTypes from 'prop-types';
引入后,就可以在组件的下方进行引用了(是在子组件的最下面。不是类里边)
XiaojiejieItem.propTypes = {
content:PropTypes.string,
index:PropTypes.number,
deleteItem:PropTypes.func
}
浏览器查看效果,发现都正常,我们修改一个错误的校验,把index改为必须是字符串
index:PropTypes.string
这时浏览器的console
里就会有报错信息
index.js:1 Warning: Failed prop type: Invalid prop `index` of type `number` supplied to `XiaojiejieItem`, expected `string`.
意思就是要求传递字符串,而我们却传递了数字过去,所以给了警告
必传值的校验
给XiaojiejieItem
加入一个personName
属性,并放入JSX
中
现在就算Xiaojiejie
不给XiaojiejieItem
传递这个值也不会报错
{this.props.personName}为您服务-{this.props.content}
现在我们从Xiaojiejie
传一个属性过来
<XiaojiejieItem
index={index}
key={index + item}
deleteItem={this.deleteItem.bind(this)}
content={item}
personName='景甜'
/>
这时候页面显示正常了
那我们如何避免必须传递personName这个属性值?
如果不传递就报错,就需要使用isRequired
关键字,它表示必须进行传递,如果不传递就报错
personName:PropTypes.string.isRequired
Warning: Failed prop type: The prop `personName` is marked as required in `XiaojiejieItem`, but its value is `undefined`.
使用默认值defaultProps
defaultProps
可以实现默认值的功能
XiaojiejieItem.defaultProps = {
personName:'黎明'
}
这样就算父组件不传personName
这个属性的话,子组件也会有默认值黎明
Ref的使用方法
在编写组件中的方法时,经常会遇到语义化模糊的代码
这对团队开发是很大的问题,code review或者合作时都会影响开发效率,必须重视react代码中的语义化。
代替原来的e.target.value
inputChange(e) {
this.setState({
inputValue: e.target.value
})
}
使用e.target
并不直观,也不好看,我们可以使用ref
来进行解决
要使用ref
,需要在JSX
中进行绑定,绑定时最好使用ES6语法中的箭头函数,这样可以简洁明了的绑定DOM元素
<input
id="serve"
className="input"
value={this.state.inputValue}
onChange={this.inputChange.bind(this)}
//关键代码——----------start
ref={(input)=>{this.input=input}}
//关键代码------------end
/>
绑定后可以把上面的方法做如下修改
inputChange(e) {
this.setState({
inputValue: this.input.value
})
}
这就使我们使我们的代码变得语义化和优雅一些,但是不建议用ref
这样操作,React是数据驱动的,用ref会出现各种问题
ref
使用的坑
要用ref绑定取得要服务的数量,先用ref进行绑定
<ul ref={(ul)=>{this.ul=ul}>
{
this.state.list.map((item, index) => {
return (
<XiaojiejieItem
index={index}
key={index + item}
deleteItem={this.deleteItem.bind(this)}
content={item}
/>
)
})
}
</ul>
绑定后可以在addList()
方法中,获取当前<div>
的值(看自己代码,子组件用的是div,不是li))
addList() {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ''
})
//关键代码--------------start
console.log(this.ul.querySelectorAll('div').length)
//关键代码--------------end
}
打开控制台,点击添加服务按钮,会发现返回数量少了一个
这个坑是因为React中的setState
是一个异步函数所这构成的
也就是说,setState
代码执行是有一个时间的,这里涉及到虚拟DOM,简单说,就是因为异步,还没有等虚拟DOM渲染,console.log就已经执行了
怎么编写才会正常呢?setState
方法提供了一个回调函数,也就是它的第二个函数
addList() {
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ''
},()={
//关键代码--------------start
console.log(this.ul.querySelectorAll('div').length)
//关键代码--------------end
})
}
这样写就正常显示了
React生命周期
React声明周期的四大阶段
Initialization
初始化阶段Mounting
挂载阶段Updation
更新阶段Unmounting
销毁阶段
什么是生命周期函数
生命周期函数指在某一个时刻组件会自动调用执行的函数
Xiaojiejie.js中,render()
函数就是一个生命周期函数,它在state发生改变时自动执行
constructor
不算生命周期函数,它叫构造函数,是ES6的基本语法
可以把它看成React的Initialization
阶段,定义属性(props)和状态(state)
Mounting阶段
Mounting阶段叫挂载阶段,伴随着整个虚拟DOM的生成
它有三个小的生命周期函数
componentWillMount
:在组件即将被挂载到页面的时刻执行render
:页面state或props发生变化时执行componentDidMount
:组件挂载完成时被执行
我们在Xiaojiejie.js
中编写三个生命周期函数来查看下
componentWillMount() {
console.log('componentWillMount---组件将要挂载到页面的时刻')
}
componentDidMount() {
console.log('componentDidMount---组件挂载完成的时刻执行')
}
render() {
console.log('render---开始挂载渲染---')
}
查看下控制台
componentWillMount---组件将要挂载到页面的时刻
render---开始挂载渲染---
react-dom.development.js:67 Warning: componentWillMount has been renamed, and is not recommended for use. See
* Move code with side effects to componentDidMount, and set initial state in the constructor.
* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.
componentDidMount---组件挂载完成的时刻执行
这也是生命周期的顺序,但是在js中书写这三个生命周期函数是没有顺序的,哪个在前哪个在后,可以随便更改
注意
componentWillMount
和componentDidMount
这两个生命周期函数,只在页面刷新时执行一次
render
函数只要有state和props变化就会执行
Updation阶段
这个阶段就是组件发生改变的更新阶段,是React生命周期比较复杂的一部分
有两个基本部分组成,一个是props
属性改变,一个是state
状态改变
shouldComponentUpdate函数
shouldComponentUpdate
函数会在组件更新之前,自动被执行
shouldComponentUpdate() {
console.log('shouldComponentUpdate---组件发生改变前执行')
}
它要求返回一个布尔类型的结果,必须有返回值,不然就会报错
Warning: Xiaojiejie.shouldComponentUpdate(): Returned undefined instead of a boolean value. Make sure to return true or false.
返回一个true
shouldComponentUpdate() {
console.log('shouldComponentUpdate---组件发生改变前执行')
return true
}
可以在控制台看到结果了,并且结果是每次文本框发生改变时都会随着改变
shouldComponentUpdate---组件发生改变前执行
render---组件挂载中---
如果返回false
,组件就不会更新了
总的来说,返回true,就同意组件更新,返回false,就反对组件更新
componentWillUpdate函数
componentWillUpdate
在组件更新之前,但是在shouldComponentUpdate
之后被执行
如果shouldComponentUpdate
返回false,这个函数就不会被执行了
shouldComponentUpdate---组件发生改变前执行
componentWillUpdate--组件更新前,shouldComponentUpdate函数之后
render---组件挂载中---
componentDidUpdate函数
componentDidUpdate
在组件更新之后执行,它是组件更新的最后一个环节
为了方便查看,在每个函数前面加上序号
1-shouldComponentUpdate---组件发生改变前执行
2-componentWillUpdate--组件更新前,shouldComponentUpdate函数之后
3-render---开始挂载渲染,组件挂载中
4-componentDidUpdate---组件更新之后执行
componentWillReceiveProps函数
我们先在Xiaojiejie.js
组件里写下这个函数
componentWillReceiveProps() {
console.log('componentWillReceiveProps')
}
会发现这个函数什么时候都不会被执行
因为Xiaojiejie.js
是一个顶层组件,它并没有接收任何的props
,可以把这个函数移动到XiaojiejieItem.js
组件中
凡是组件都有生命周期函数,所以子组件也是有的,并且子组件接收了props
,这时这个函数就可以被执行了
componentWillReceiveProps() {
console.log('child - componentWillReceiveProps')
}
1-shouldComponentUpdate---组件发生改变前执行
2-componentWillUpdate--组件更新前,shouldComponentUpdate函数之后
3-render---组件挂载中---
child - componentWillReceiveProps
4-componentDidUpdate---组件更新之后执行
总结一下,子组件接收到父组件传递过来的参数,父组件render函数重新被执行,这个生命周期就会被执行
- 这个组件第一次存在于Dom中,函数是不会被执行的
- 如果已经存在于Dom中,函数才会被执行
Unmounting阶段
componentWillUnmount函数
这个函数在组件从页面中删除的时候执行
在XiaojiejieItem.js
中写
// 当组件从页面中删除的时候执行
componentWillUnmount() {
console.log('child - componentWillUnmount')
}
点击服务项,服务项被删除时,这个函数就被执行了
1-shouldComponentUpdate---组件发生改变前执行
2-componentWillUpdate--组件更新前,shouldComponentUpdate函数之后
3-render---组件挂载中---
child - componentWillReceiveProps
child - componentWillUnmount
4-componentDidUpdate---组件更新之后执行
生命周期改善程序性能
小姐姐组件存在性能问题
子组件XiaojiejieItem
频繁无用渲染render
在React Developer Tools中点开设置,选中highlight Updates
这时在浏览器的文本框中输入内容,可以清楚地看到子组件也发生了重新render
的情况
很多人会忽略这样的性能损耗,认为没有什么大不了的,但是软件的卡顿是一点点产生的,所以必须要减少性能损耗
可以在XiaojiejieItem.js
的render
函数里加入下面代码方便查看子组件渲染的情况
render() {
console.log('child-render')
return (
<div onClick={this.handleClick}>
{this.props.personName}为您服务-{this.props.content}
</div>
);
}
利用shouldComponentUpdate
解决
直接在XiaojiejieItem.js
中加入下面的代码
shouldComponentUpdate() {
return false;
}
现在问题已经没有了,但这样做太暴力,否定了所有的东西
在真实项目中,增删改查肯定是需要的,需要改变值的属性值,达到渲染就没办法了
我们可以优化一下,当添加一项服务时,子组件才会渲染
shouldComponentUpdate有两个参数:
- nextProps:变化后的属性
- nextState:变化后的状态
shouldComponentUpdate(nextProps,nextState) {
if(nextProps.content !== this.props.content){
return true
}else {
return false
}
}
这算是完美解决了子组件的渲染性能问题
面试React时,写TODOList应用,都是看这个类区分等级的
能写出来的,算是普通程序员
能写出来并做性能优化的,这算有经验的程序员
参考资料
https://www.jianshu.com/p/514fe21b9914
https://jspang.com/