继《一、React基础知识再出发(一)》之后,本篇文章,我们接着学习React的基础知识。
一、PropType与DefaultProps
我们已经知道,在React中父组件可以向子组件传递数据(state
值和函数
),那么在子组件中,我们可以不可以对从父组件传递过来的值做一些校验和设置默认值。肯定是可以的,可能过程会比vue中稍微复杂一点。
vue中校验参数和默认值设置
<script>
export default {
props: {
username: {
type: string,
default:function(){
return 'antiai'
}
}
}
}
</script>
React参数校验和默认值设置
...
import PropTypes from 'prop-types'
class TodoItem extends Component{
...
// 定义值的类型
static propTypes = {
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number
}
}
// 设置默认值
static defaultProps = {
test: 'hello world'
}
在React中对值的校验和默认值的设定,确实要比vue中的复杂一点。当然校验的规则还有很多,例如
static propTypes = {
test:PropTypes.string.isRequired,
test1:PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
])
}
具体的逻辑请参照https://reactjs.org/docs/typechecking-with-proptypes.html。
二、React中的ref(refrence)
ref
用来获取原生DOM节点,这一点跟vue中类似。不过在React中的使用会相对复杂一点。
定义ref
<input
className="input"
id="insertArea"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input) => {this.input = input}}
/>
我们需要给ref
传递一个函数,如上,this.ul
就是input的原生节点。
使用ref
handleInputChange(e){
let value = this.input.value
this.setState({
inputValue: value
})
}
在React中,我们是不推荐使用ref
的方式直接操作原生DOM节点的,我们更加推荐使用通过操作state
数据方式修改数据。ref
虽然能够实现功能,使用它会存在一定的风险。比如动态获取列表元素的节点个数。示例代码如下:
return (
<Fragment>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
className="input"
id="insertArea"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input) => {this.input = input}}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul ref={(ul) => {this.ul = ul}}>
{
this.getItem()
}
</ul>
</Fragment>
)
handleBtnClick(){
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ''
})
alert(this.ul.querySelectorAll('div').length)
}
如上代码,我们直接获取DOM节点的个数时,第一次为0,第二次才是1,这是因为this.setState
是异步更新数据的。要解决这问题,我们可以按如下的操作,将获取节点的操作放在this.setState
第二个参数中。
handleBtnClick(){
this.setState({
list: [...this.state.list, this.state.inputValue],
inputValue: ''
},() => {
alert(this.ul.querySelectorAll('li').length)
})
}
三、React中的生命周期钩子函数
首先来看一下React生命周期go函数的图示吧。
乍一看,很多,vue中也就不过才8个生命周期函数,这里似乎有十几个,而且这个概念图的设计也没有vue的好看。有点头疼。但是当我们仔细看的时候,其实并没有我们想象中的那么难。从图中,我们将所有的钩子函数按照执行的时机分为四大类。分别是
initation、Mounting、Updation、Ummounting
。首先,我们需要明白生命周期钩子函数是什么?生命周期函数指在某一时刻组件会自动调用执行的函数下面,我们将逐一的学习这四个阶段中的钩子函数。
1、 初始化阶段(Initation)
按照上面的对生命周期钩子函数的描述。构造函数constructor
是应该是生命周期钩子函数,在组件被创建的时候调用,但是构造函数是JavaScript语言的特性,并非React独有,所以不能端是生命周期钩子函数。
2、 挂载阶段(Mounting)
1、componentWillMount:在组件即将被挂载到页面的时刻自动执行
// 在组件即将被挂载到页面的时刻自动执行
componentWillMount(){
console.log('componentWillMount:','在组件即将被挂载到页面的时刻自动执行')
}
2、componentDidMount:组件被挂载到页面之后执行
componentDidMount(){
console.log('componentDidMount:','组件被挂载到页面之后执行')
}
3、 更新阶段(Updation)
1、shouldComponentUpdate:组件被更新之后自动执行
这个方法需要返回一个boolean值,如果为true
表示执行后面的操作,否则不执行后面的操作。
shouldComponentUpdate(){
console.log('shouldComponentUpdate', '组件被更新之后自动执行')
return true
}
2、componentWillUpdate:
组件被更新之前,她会自动执行,但是他在shouldComponentUpdate之后执行
如果shouldComponentUpdate返回true它才执行
如果shouldComponentUpdate返回false,这个函数就不会执行了
componentWillUpdate(){
console.log('componentWillUpdate')
}
3、componentDidUpdate:组件更新完成之后,他会被触发
componentDidUpdate(){
console.log('componentDidUpdate', '组件更新完成后,它会被执行')
}
4、componentWillReceiveProps:子组件接受到父组件参数的时候触发!
一个组件要从父组件接受参数
如果这个组件第一次存在与父组件中,不会执行
如果这个组件之前已经存在父组件中,才会执行
componentWillReceiveProps(){
console.log('componentWillReceiveProps', '子组件接受到父组件参数的时候触发!')
}
4、 解绑阶段(Ummounting)
1、componentWillUnmount:当组件即将被从页面中踢出的时候,会被执行
componentWillUnmount(){
console.log('Child componentWillUnmount')
}
下面表格列出的是vue和react中生命周期钩子函数对比图:
React | Vue | 执行时机 |
---|---|---|
- | beforeCreate | 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用 |
- | created | 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用 |
componentWillMount | beforeMount | 组件即将被挂载到页面的时刻 |
render | - | - |
componentDidMount | mounted | 组件被挂载到页面之后 |
shouldComponentUpdate | - | 组件被更新之后自动执行 |
componentWillUpdate | beforeUpdate | 组件被更新之后自动执行(shouldComponentUpdate返回为true的情况下) |
componentDidUpdate | updated | 组件更新完成之后,他会被触发 |
componentWillReceiveProps | - | 子组件接受到父组件参数的时候 |
- | destroyed | 组件即将被从页面中踢出前的时候 |
componentWillUnmount | destroyed | 组件即将被从页面中踢出的时候 |
5、 生命周期钩子函数的使用场景
通常情况下,父组件数据发生变化会触发其render
函数的执行,同时子组件的render
函数也会被执行,这样性能会很差,有没有好的办法呢。我们可以在shouldComponentUpdate
这个生命周期钩子函数处理我们的逻辑,判断前后输入的值是否有改变,有改变则返回true
,继续执行子组件下面的render
函数。
shouldComponentUpdate(nextProps, nextState){
if(nextProps.content !== this.props.content){
return true
} else {
return false
}
}
但是,从我测试的结果来看,这个优化放似乎已经起不到作用了,react
内部或许已经做了优化,这一点需要稍后确认一下。
四、React中优化的方案
1、在父子组件通信的时候,子组件使用shouldComponentUpdate
钩子函数进行优化。
2、在constructor
中绑定函数的this调用
constructor(props){
super(props)
this.state = {
inputValue: '',
list: []
}
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleDelete = this.handleDelete.bind(this)
}
3、采用虚拟DOM和Diff算法进行优化。
4、使用this.setState
进行异步操作。
this.setState({
inputValue: value
})
五、React中的动画
React中的动画跟vue中的动画使用方式相差不大,主要有下面几种方式:
1、CSS过渡动画
这种动画是动画实现的最简单的一种。主要使用css3的transition
属性实现。
constructor(props){
super(props)
this.state = {
show: true
}
this.handleToggle = this.handleToggle.bind(this)
}
render(){
return (
<Fragment>
<p className={this.state.show ? 'show' : 'hide'}>css transtion animation</p>
<button onClick={this.handleToggle}>Toggle</button>
</Fragment>
)
}
handleToggle(){
this.setState({
show: !this.state.show
})
}
.show{
opacity: 1;
transition: all 1s ease-in;
}
.hide{
opacity: 0;
transition: all 1s ease-out;
}
2、CSS动画效果
这种方式跟上面的过渡动画相似,不同之处主要在于将transition
替换成了@keyframes
动画。
.show{
animation: show-item 2s ease-in forwards;
}
.hide{
animation: hide-item 2s ease-in forwards;
}
@keyframes show-item{
0%{
opacity: 0;
color: red;
}
50%{
opacity: 0.5;
color: green;
}
100%{
opacity: 1;
color: blue;
}
}
@keyframes hide-item{
0%{
opacity: 1;
color: red;
}
50%{
opacity: 0.5;
color: green;
}
100%{
opacity: 0;
color: blue;
}
}
3、react-transition-group实现动画
在处理一些复杂的动画场景的时候,上面两种动画会显得有点鸡肋,这时候,我们可以更加强大的动画库react-transition-group
来完成我们的需求。
yan add react-transition-group
import { CSSTransition } from 'react-transition-group'
render(){
return (
<Fragment>
<CSSTransition
in={this.state.show}
timeout={1000}
classNames='fade'
unmountOnExit
appear={true}
onEntered={(el) => {el.style.color='blue'}}>
<p>css transtion animation</p>
</CSSTransition>
<button onClick={this.handleToggle}>Toggle</button>
</Fragment>
)
}
上面的参数表示:
属性 | 作用 |
---|---|
in | 控制何时显示 |
timeout | 动画的过渡时间 |
classNames | 动画的过渡的名称 |
unmountOnExit | 动画结束后会移除动画节点 |
onEntered | 动画结束之后调用的生命周期钩子函数 |
appear | 为true时第一次也会产生动画 |
当然react-transition-group
的属性不止这些,您可以自行查阅文档。另外,我们还需要有css的配合。关于css的类名的解释如下:
.fade-enter, .fade-appear{
opacity: 0;
}
.fade-enter-active, .fade-appear-active{
opacity: 1;
transition: opacity 1s ease-in;
}
.fade-enter-done{
opacity: 1;
}
.fade-exit{
opacity: 1;
}
.fade-exit-active{
opacity: 0;
transition: opacity 1s ease-in;
}
.fade-exit-done{
opacity: 0;
}
属性 | 作用时机 |
---|---|
.fade-enter | 入场动画第一帧 |
.fade-enter-active | 入场动画第二帧到动画结束之前 |
.fade-enter-done | 入场动画完成的那一刻 |
.fade-exit | 出场动画第一帧 |
.fade-exit-active | 出场动画第一帧到动画结束之前 |
.fade-exit-done | 出场动画完成的那一刻 |
.fade-appear | 首次加载也使用动画 |
.fade-appear-active | 首次加载也使用动画 |
4、react-transition-group实现多元素动画
示例代码如下:
import { CSSTransition, TransitionGroup } from 'react-transition-group'
render(){
return (
<Fragment>
<ul>
<TransitionGroup>
{
this.state.list.map((item, index) => {
return (
<CSSTransition
timeout={1000}
classNames='fade'
unmountOnExit
appear={true}
onEntered={(el) => {el.style.color='blue'}}
key={index}>
<li >{item}</li>
</CSSTransition>
)
})
}
</TransitionGroup>
</ul>
<button onClick={this.handleAddItem}>Toggle</button>
</Fragment>
)
}
handleAddItem(){
this.setState((prevState) => {
return {
list: [...prevState.list, 'item']
}
})
}
这个地方也没有什么地方需要过多解释,如有问题,可在评论区交流,本篇文章结束。