React组件三大核心属性及生命周期钩子

1,650 阅读8分钟

React组件三大核心属性

这一篇主要写了React的组件三大核心属性state、props、refs,并通过一些小案例进行演示!

state

state是组件对象最重要的属性,值是对象(可以包含多个key—value组合),组件被称为'状态机',通过更新组建的state来更新对应页面的显示。

通过状态实现一个简单的天气切换

天气7.gif

  // 优化前
  class Weather extends React.Component{
   constructor(props){
    super(props)
    this.state = {isHot:false,wind:'微风'}
    //解决changeWeather中this指向问题
    this.changeWeather = this.changeWeather.bind(this)
   }
   render(){
    console.log('render');
    //读取状态
    const {isHot,wind} = this.state
    return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
   }
   changeWeather(){
    //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
    //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
    //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
    //获取原来的isHot值
    const isHot = this.state.isHot
    this.setState({isHot:!isHot})
   }
  }
  ReactDOM.render(<Weather/>,document.getElementById('test'))
  // 优化后
  class Weather extends React.Component{
   state = {isHot:false,wind:'微风'}
   render(){
    const {isHot,wind} = this.state
    return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
   }
   //自定义方法————要用赋值语句的形式+箭头函数
   changeWeather = ()=>{
    const isHot = this.state.isHot
    this.setState({isHot:!isHot})
   }
  }
  ReactDOM.render(<Weather/>,document.getElementById('test'))
1. 在创建完组建之后首先需要通过constructor构造器进行初始化状态
2. 初始化完成后再render中就可以访问到
3. 通过onClick(在react中属性需要实用小驼峰写)绑定点击事件changeWeather
# 注意 
changeWeather放在哪里? ————  Weather的原型对象上,供实例使用,但是由于changeWeather是作为点击事件的回调,并不是通过(实例.方法)的形式调用的,且类中的方法默认开启了局部严格模式,所以changeWeather中的this为undefined。
4. 解决这个问题有两种方法
   1. 通过bind修改this指向来解决
   2. 将changeWeather = ()=>{} 写成赋值语句的形式,将方法放到Weather类上,这样changeWeather方法就从原型上的方法变成了实例自身的方法

# 修改方法
自定义方法用赋值语句的形式+箭头函数创建,就不需要在构造器中强制绑定this了,直接初始化state状态,就不用在构造器中初始化了

# 复盘
1. 构造器constructor只调用一次
2. render初始化调用一次,加状态更新几次render执行几次
3. changeWeather点击几次调用几次

1. 状态必须通过setState进行更新,且更新是一种合并,不是替换,可以直接传入一个对象进行修改也可以传入一个函数通过返回值修改

props

每个组件对象都会有props属性,组件标签的所有属性都保存在props中,通过标签属性从组件外向组件内传递变化的数据,组件内部不能修改props数据,props是只读的

简单使用

  class Person extends React.Component{
   render(){
    const {name,age,sex} = this.props
    return (
     <ul>
      <li>姓名:{name}</li>
      <li>性别:{sex}</li>
      <li>年龄:{age}</li>
     </ul>
    )
   }
  }
  ReactDOM.render(<Person name="jerry" age={19}  sex="男"/>,document.getElementById('app'))
// 简化写法
......
const p = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p}/>,document.getElementById('app'))
// 对props进行内容限制及默认值设置

// 引入prop-types,用于对组件标签属性进行限制
<script type="text/javascript" src="../js/prop-types.js"></script>
  //对标签属性进行类型、必要性的限制
  class Person extends React.Component{
    ......
    Person.propTypes = { // 给类自身加内容可以直接写 static propTypes = 进行简写
    name:PropTypes.string.isRequired, //限制name必传,且为字符串
    sex:PropTypes.string,//限制sex为字符串
    age:PropTypes.number,//限制age为数值
    speak:PropTypes.func,//限制speak为函数
    }
    //指定默认标签属性值
    Person.defaultProps = {
    sex:'男',//sex默认值为男
    age:18 //age默认值为18
    }
  }
  ....

refs

Refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素,正常我们操作节点需要DOM API来进行操作,但是这样违背了 React 的理念,因此有了refs。

  // 通过回调形式完成的!
  //创建组件
  class Demo extends React.Component{
   //展示左侧输入框的数据
   showData = ()=>{
    const {input1} = this
    alert(input1.value)
   }
   //展示右侧输入框的数据
   showData2 = ()=>{
    console.log(this);
    const {input2} = this
    alert(input2.value)
   }
   render(){
    return(
     <div>
      <input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>&nbsp;
      <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
      <input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>&nbsp;
     </div>
    )
   }
  }
  //渲染组件到页面
  ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('app'))

字符串形式

给DOM元素添加ref="input",这样就可以通过this.refs获取这个input标签,这个就是字符串形式的回调

<input ref="input" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>

回调形式refs(开发常用)

给DOM元素添加ref属性传递一个回调函数c => this.input = c(箭头函数简写),这样会将DOM节点放在组件实例上,直接使用this.input进行调用即可

<input ref={c => this.input = c } type="text" placeholder="点击按钮提示数据"/>

缺点:

如果ref回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React需要清空旧的ref并且设置新的。

解决方法:

通过将 ref 的回调函数定义成class的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

  // 先给DOM元素添加ref属性,ref={this.saveInput},该方式可以避免内联函数的方式,在更新过程会执行两次的问题。
  ......
  saveInput = (c)=>{
  this.input1 = c;
  }
<input ref={this.saveInput} type="text"/><br/><br/>

createRef的refs

  ......
  // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
  myRef = React.createRef()
  showData = ()=>{
  alert(this.myRef.current.value);
  }
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;

事件处理

React使用的是自定义事件,而不是原生的 DOM 事件(为了更好的兼容性)。React的事件是通过事件委托方式处理的(委托给组件最外层的元素)为了更高效,能够通过事件的 event.target获取发生的 DOM 元素对象,要尽量减少 refs的使用。当发生事件的元素正好是要操作的元素,可以通过事件的 event.target获取发生的 DOM 元素对象!

  ......
  //展示右侧输入框的数据
  showData2 = (event)=>{
    // 通过事件的 event.target获取发生的 DOM 元素对象
    alert(event.target.value);
  }
<input onBlur={this.showData} type="text" placeholder="失去焦点提示数据"/>

受控组件和非受控组件

受控组件:随着你的输入,维护状态就是受控。类似Vue里的双向数据绑定。避免了使用ref!
非受控组件:现用现取就是非受控,使用了ref!

小案例

输入用户名和密码点击登录,弹窗出现用户名和密码!

天气7.gif

  //受控组件方式 
  class Login extends React.Component{

   //初始化状态
   state = {
    username:'', //用户名
    password:'' //密码
   }

   //保存用户名到状态中
   saveUsername = (event)=>{
    this.setState({username:event.target.value})
   }

   //保存密码到状态中
   savePassword = (event)=>{
    this.setState({password:event.target.value})
   }

   //表单提交的回调
   handleSubmit = (event)=>{
    event.preventDefault() //阻止表单提交
    const {username,password} = this.state
    alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
   }

   render(){
    return(
     <form onSubmit={this.handleSubmit}>
      用户名:<input onChange={this.saveUsername} type="text" name="username"/>
      密码:<input onChange={this.savePassword} type="password" name="password"/>
      <button>登录</button>
     </form>
    )
   }
  }
  //渲染组件
  ReactDOM.render(<Login/>,document.getElementById('test'))
  // 非受控组件写法
  class Login extends React.Component{
   handleSubmit = (event)=>{
    event.preventDefault() //阻止表单提交
    const {username,password} = this
    alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
   }
   render(){
    return(
     <form onSubmit={this.handleSubmit}>
      用户名:<input ref={c => this.username = c} type="text" name="username"/>
      密码:<input ref={c => this.password = c} type="password" name="password"/>
      <button>登录</button>
     </form>
    )
   }
  }
  //渲染组件
  ReactDOM.render(<Login/>,document.getElementById('test'))

高阶函数

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数:
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等..

函数柯里化

函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。 
function sum(a) {
  return (b) => {
    return (c) => {
      return a + b + c
    }
  }
}
console.log(sum(1)(2)(3));

通过函数柯里化对受控组件小案例优化

......
saveFormData = (dataType)=>{
  return (event)=>{
    this.setState({[dataType]:event.target.value})
  }
}
<form onSubmit={this.handleSubmit}>
    用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
    密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
    <button>登录</button>
</form>
......
// 注意:onChange事件里必须是一个函数,所以要对saveFormData函数进行柯里化,使得this.saveFormData('username')依然返回一个函数。

React的生命周期钩子

在 React 中为我们提供了一些生命周期钩子函数,让我们能在 React 执行的重要阶段,在钩子函数中做一些事情。那么在 React 的生命周期中,下面来总结一下(可以参考React官网的生命周期图):

React 生命周期(新)主要包括三个阶段:初始化阶段,更新阶段,销毁阶段,如下图所示:

3_react生命周期(新).png

1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
constructor()
getDerivedStateFromProps
render()
componentDidMount() =====> 常用,一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
getDerivedStateFromProps
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate
componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount()  =====> 常用,一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

附一张就旧的生命周期图,做对比用!

2_react生命周期(旧).png

小结

通过这一篇能够学到三大核心属性和生命周期钩子,加油!