React学习(6)-React中组件的数据-state

前言

  • 组件中的state具体是什么?怎么更改state的数据?javascript

  • setState函数分别接收对象以及函数有什么区别?css

  • 如何划分组件的状态数据,进行自个人灵魂拷问,以及props与state的灵魂对比java

那么本节就是你想要知道的react

React中组件的数据-内附有视频算法

React中的state

一个组件最终渲染的数据结果,除了prop还有state,state表明的是当前组件的内部状态,你能够把组件当作一个'状态机",它是可以随着时间变化的数据,更多的是应当在实现交互时使用,根据状态state的改变呈现不一样的UI展现编程

在React中,由于不能直接修改外部组件传入的prop值数组

当须要记录组件自身数据变化时,想要使组件具有交互的能力,那么须要有触发该组件基础数据模型改变的能力,那么此时就须要使用state浏览器

一旦组件的状态(数据)发生更改,组件就会自动的调用render函数从新渲染UI,更改这个state状态是经过this.setState方法来触发实现的bash

下面咱们从一个简单的点击按钮,显示和隐藏的效果开始: 效果以下所示: 连续点击按钮,上方的itclanCoder文本在显示和隐藏进行切换,当状态为true时,itclanCoder文本显示,状态为false时,itclanCoder文本隐藏,注意控制台调试器 服务器

state状态的切换.gif
具体代码以下所示:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';

class Button extends Component {
  constructor(props){
  super(props);

  // 初始化state
  this.state = {
    isShow: true
  }
}

render(){
return(
   <div style = {{ textAlign: "center"}}>
     <div className = { this.state.isShow?"showText":"hiddenText" }>itclanCoder</div>
     <div>{ String(this.state.isShow) }</div>
     <div><button onClick = { this.handleBtnClick }>点击按钮切换文本状态</button></div>
</div>
);
}

handleBtnClick = () => {
  this.setState({
    isShow: !this.state.isShow
  });
}
}

const container = document.getElementById('root');

ReactDOM.render(<Button />, container);
复制代码

经过上面的代码,能够看出初始化state数据,通常在组件的构造器结尾处进行编写

在上面的Button组件内,经过对this.state的赋值,完成了对该Button组件内部state的初始化

注意:

  • this.state放置的位置:应当放在构造器函数内进行使用的,不然是会报错的

  • 初始化该组件当前状态的state值必须是一个javascript对象,不能是string,或者number,boolean等简单的基本数据类型

  • 即便你想要存储一个只是数字类型的数据,也只能把它存做state对象下的某个字段对应的值中,这个state能够看作是组件自身提供的一个固定的对象,用于存储当前组件自身的状态,它是私有的对象,而且彻底只受控于当前组件

在以上代码中,经过给button按钮监听绑定onClick属性挂载点击事件处理函数(上面是handleBtnClick),来达到控制组件state中的isShow这个状态,从而让文本显示仍是隐藏

显示和隐藏是经过添加class层叠样式进行设置,可是控制这个行为切换动做的,倒是js

这里用的是箭头函数,若是不用此方法,必定要记得用bind进行this坏境的绑定

在代码中,经过this.state能够读取当前组件状态的state,可是想要改变state的状态,并非直接经过this.state进行更改,而是经过React内置提供的一个setState方法进行触发的

为了解释不能直接更改this.state,咱们来看另外一个加减数字的例子,代码以下所示

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import "./index.css";


class Count extends Component {
constructor(props){
super(props);

this.state = {
   count: 0
}

  // this坏境的绑定
  this.handleBtnIncrease = this.handleBtnIncrease.bind(this);
  this.handleBtnReduce = this.handleBtnReduce.bind(this);
}

render(){
return (
<Fragment>
  <div style = {{textAlign: "center"}}>
    <button onClick = { this.handleBtnReduce }>-</button>
    <span className = "text">{ this.state.count}</span>
  <button onClick = { this.handleBtnIncrease }>+</button>
</div>
</Fragment>
);
}

  handleBtnReduce() {
    this.setState({
      count: this.state.count-1
    });
  }

  handleBtnIncrease() {
   // 尝试直接更改this.state的值,这样是有问题的
   this.state.count = this.state.count+1;
  }
}

const container = document.getElementById('root');

ReactDOM.render(<Count />, container);
复制代码

当你点击加按钮的时候,页面不会有任何反应,打开控制台,会有一个警告提示 不要直接的更改state的值,当你在点击减号时,你会发现计数发生阶跃性变化,好比初始计数值是0的状况下,在你连续点击加按钮三次时,计数值没有发生任何变化

可是当你点击减号时计数值就会变成2,这个就很是诡异了,效果以下所示

直接修改this.state会产生诡异的bug.gif
直接修改this.state的值,虽然改变了组件的内部状态,可是并无驱动组件进行从新渲染,既然组件没有从新渲染,页面上的UI这个this.state固然不会有任何变化

可是React中的setState方法却可以触发页面的渲染,它能够接收一个对象或者函数

正确的写法应当是:利用setState进行对组件state的更改

handleBtnIncrease() {
  this.setState({
    count: this.state.count+1;
  });
}
复制代码

React中setState要知道的

定义: setState方法是React中React.Component组件所提供的一个内置的方法,当你调用这个setState方法的时候,React会更新组件的状态state,而且从新调用render方法,最终实现当前组件内部state的更新,从而最新的内容也会渲染到页面上

做用:修改组件的内部state的状态,每每用于更新用户界面以响应事件处理器和处理服务器数据的主要方式

参数:setState函数接收参数有两种方式,一个是对象,另外一个是函数

注意事项

不能直接修改state,它并不会从新渲染组件,以下所示

// 错误的写法 this.state.xxx = "新的值"
this.state.count = this.state.count+1;
复制代码

应该使用setState()函数去更新当前组件的状态

<!--this.setState({-->
<!-- xxx: "新的值" -->
<!--});-->
this.setState({
  count: this.state.count+1
})
复制代码

通常而言,经过在React中封装的事件,例如:onChange,onClick,onKeyDown,onFocus,onBlur等这些事件类型里面绑定事件方法内的setState都是异步的

有时候,this.props和this.state可能会异步更新,在调用setState以后,并不会立马更新组件

其实它是会批量延迟更新

也就是props,state的值并不会立马的映射更新,它是把这个state对象放到一个更新队列里面,而后从队列当中把新的状态提出来合并到state中,最后在触发render函数组件的更新,从而致使UI界面的改变

你不能依赖它来更新下一个状态

对于SetState何时同步何时异步?若是是React控制的事件处理程序以及在它的钩子(生命周期)函数内调用setState,它不会同步的更新state

也就是说:React控制以外的事件调用setState是同步更新的,例如原生js绑定的事件,setTimeout/setInterval等,固然在React中绝大多数都是异步处理的

对于实现同步,咱们能够看一下下面这个代码,先看下效果:点击减号(-)按钮,页面上count变化与控制台上的值的对应关系,点击加(+)按钮与另加按钮,观看控制台也页面UI效果

setState异步.gif

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import "./index.css";


class Count extends Component {
  constructor(props){
    super(props);

    this.state = {
      count: 10
    }

   // this坏境的绑定
   this.handleBtnIncrease = this.handleBtnIncrease.bind(this);
   this.handleBtnReduce = this.handleBtnReduce.bind(this);

}

render(){

return (
  <Fragment>
    <div style = {{textAlign: "center"}}>
      <button onClick = { this.handleBtnReduce }>-</button>
      <span className = "text">{ this.state.count}</span>
      <button onClick = { this.handleBtnIncrease }>+</button>
      <button id="btn-add">另加</button>
    </div>
</Fragment>
);
}
// 经过React绑定监听的onClick事件类型绑定的方法内的setState方法都是异步的
handleBtnReduce() {
  this.setState({
    count: this.state.count-1
  });
  console.log("点击减-count值",this.state.count);
}

handleBtnIncrease() {
setTimeout(() => {
  this.setState({
    count: this.state.count+1
  });
  console.log("点击加-count的值", this.state.count);
},10);

}

// 非React绑定的事件类型方法内调用的setState,是同步的
componentDidMount() {
  const btnAdd = document.getElementById('btn-add');
  btnAdd.addEventListener('click', () => {
     this.setState({
       count: this.state.count+1
     });
    console.log(this.state.count);
});
}
}

const container = document.getElementById('root');

ReactDOM.render(<Count />, container);
复制代码

以上经过setTimeout/setInterval等addEventListener,以js的事件绑定方式内调用setState方法,此时,state的值将是同步更新的

若是要追究setState内部执行过程,其实它是很复杂的,包括了更新state,以及各个生命周期函数,之后有时间单独在详聊的

在这里,你只须要只知道,对于在React中的JSX绑定的事件处理函数中调用setState方法是异步的就能够了

若是你须要基于当前的state来计算出新的值,那么setState函数就应该传递一个函数,而不是一个对象,它能够确保每次调用的都是使用最新的state,这一点正是取决因而否传对象和函数的区别

多个setState调用会合并处理

当在事件处理方法内屡次调用setState方法时,render函数只会执行一次,并不会致使组件的重复渲染,由于React会将多个this.setState产生的修改放在一个队列里面进行批量延迟处理,因此从这点上讲,React设计这个setState函数是很是高效的,结合了函数式编程,不用考虑性能的问题

以下代码所示: 在事件处理程序内调用setState方法改变state的值,虽然是两次调用可是并不会引发render函数的重复渲染,它会合并成到一个队列中执行一次操做,只有state或者props发生改变时,它才会引发render函数的从新渲染

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import "./index.css";


class ChangeText extends Component {
   constructor(props){
     super(props);

    this.state = {
       desc: "欢迎关注微信itclanCoder公众号",
       isStatus: true
    }

   // this坏境的绑定
   this.handleChangeText = this.handleChangeText.bind(this);
}

render(){
console.log("render变化了");
   const name = this.state.isStatus? this.props.name:"随笔川迹";
   const age = this.state.isStatus? this.props.age: 20;
   return (
     <Fragment>
       <div style = {{textAlign: "center"}}>
         <div>{ this.state.desc }</div>
         <div>{ name },永远的{ age }岁</div>
         <button onClick = { this.handleChangeText }>点击按钮改变上方文字</button>
       </div>
</Fragment>
);
}

handleChangeText() {
  this.setState((prevState, newProps) => ({
     isStatus: !prevState.isStatus
  }));

  this.setState({
    desc: "学习React",
  });

}

}

ChangeText.defaultProps = {
  name: "川川",
  age: 25

}

ChangeText.propTypes = {
  name: PropTypes.string,
  age: PropTypes.number
}

const container = document.getElementById('root');

ReactDOM.render(<ChangeText name="川川,一个帅小伙" age={ 18 } />, container);
复制代码

刷新浏览器,查看render函数执行的次数,当点击按钮时,只要state和props发生了改变,render函数就会从新渲染

屡次调用setState方法只会执行一次.gif
从上面的代码中, 在事件处理函数中调用setState方法时,当setState函数传递的是一个函数时,这个函数接收两个形参数,第一个参数prevState(参数名任意),是先前组件状态时的state,然后一个参数newProps(形参名任意)是这次更新被应用时的props,它不是必传的,具体视状况而定

直到如今,知道给setState函数传递一个对象与传递一个函数的区别是什么?

传递一个函数可让你在函数内访问到当前的state的值,由于setState的调用是异步的,this.state.以及this.props不会当即更新,它会被放置到一个队列中延迟合并处理

只有当state和props数据发生改变时,render函数才会从新渲染

因此你是能够链式的进行更新,并确保它们是创建在另外一个之上的,这样不会发生冲突

这也正是setState函数传递一个函数的缘由,绝大多数时候,最优的方式是,你传递一个函数给setState就能够了,并给该函数传递两个形参,而后经过当中的形参来更新state就能够避免诡异的bug了

小结一下:

setState函数是用于更新当前组件的状态的,不只能够更改props也能够更改state

它接收两种参数形式,一个是对象,另外一个是函数

当须要基于当前的state计算出新的值进行处理,给setState函数应该传递一个函数而不是对象,这样能够保证每次调用的状态值都是最新的

至于为何React不选择同步更新this.state

这是由于React是有意这么设计的,作异步等待,在constructor构造器函数执行完后,在执行render函数,直到全部组件的事件处理函数内调用setState函数完成以后,避免没必要要的从新渲染来提高性能

你能够能会想,React不能对this.state进行立马更新,而不对组件进行从新渲染呢

若是this.state能当即更新改变,就会破坏组件的协调,只有当props或者state发生改变时,React经过将最新返回的JSX元素与原先的元素进行对比(diff算法),来决定是否有必要进行一次DOM节点的更新,若是先后JSX元素不相等,那么React才会更新DOM

若是props或者state能被直接被修改,将会破坏组件复用的原则,会出现一些莫名其妙的bug

如何划分组件的状态数据

不管是props仍是state都是组件的数据,影响组件最终的UI展现,究竟怎么样进行区分,哪一个组件应该拥有某个state状态,进行设置,有时候,它们是很是模糊的概念

可是在React中应该遵循一些原则:

让组件尽量的少状态

若是该组件只是用于UI渲染,数组展现,并没有复杂的页面逻辑交互,那么应该让组件的数据定义成props,经过外部组件传入,而并不是将数据设置到状态当中去

那么究竟什么样的数据属性能够视为状态?

状态(state)应该是会随着时间产生变化的数据,当更改这个状态(state),须要更新组件的UI,就能够将它定义成state,更可能是在实现页面的交互时使用的

另外一种程度上讲,在写静态,没有任何交互页面时,不该该用state定义当前组件的状态用来填充页面

而应该能用外部世界(组件)传来的数据,就用外部组件传来的props进行数据的填充

下面的这些就不是状态(state),不该该定义成state,如何断定该用props仍是state,能够进行自个人”灵魂拷问“

  • 该数据是否由父组件(外部世界)经过props传递给子组件而来的?若是是,那么它就不是state

  • 经过state或者props能够计算出的数据:好比一个数组的长度等,那么它就不是state

  • 它是否随着时间的变化而保持不变?若是不改变,那么它也不该该是state:例如:某些页面固定的标题,字段

  • 与props重复的数据,除非这个数据后期是须要作变动的

而针对这种无状态的组件(UI组件/函数式组件)

能够用纯粹的函数来定义,所谓纯函数,只有输入和输出,无状态,无生命周期钩子函数,只是用做于接收父组件传来的props值渲染生成DOM结构,无交互,无逻辑层的数据展现

无状态(函数式)组件,在性能上是最高效的,开销很低,由于没有那些生命周期函数嘛

就是一普普统统的函数,执行效率是很高的

UI = render(data)
复制代码

还记得上次提过上面的公式?React组件扮演的角色应该就是一个纯函数(UI组件),它是没有任何反作用的,因为组件的复用性原则,是不能直接修改props的值的

若是该组件只用于作数据层展现,无需添加生命周期函数等,就能够毫无悬念的使用无状态组件去定义,固然用箭头函数也是能够的,它就是普通函数一简写的替换,可是要注意,箭头函数没有this的绑定

const Header = (props) => {
return (
  <div>Hello, {props.content}</div>
);
}
const container = document.getElementById('root')
ReactDOM.render(<Header content="itclanCoder">, container)
复制代码

props与state的灵魂对比

共同点:

都是组件内的数据,是一普通的javascript对象,都是用来保存信息的,这些信息能够控制组件的形态

不一样点:

props是由父组件传入的(相似形参),用于定义外部组件的接口,是React组件的输入,它是从父组件传递给子组件的数据对象,在父(外部)组件JSX元素上,以自定义属性的形式定义,传递给当前组件,而在子组件内部,则以this.props或者props进行获取

props只具有读的能力,不能直接被修改,若是想要修改某些值,用来响应用户的输入或者输出响应,能够借用React内提供的setState函数进行触发,并用state来做为替代

state是当前组件的内部状态,它的做用范围只局限于当前组件,它是当前组件的一个私有变量.用于记录组件内部状态的,若是组件中的一些数据在某些时刻发生变化,或者作一些页面逻辑交互时,须要更新UI,这个时候就须要使用state来跟踪状态(例如控制一元素的显示隐藏来回切换等状态),它由组件自己管理,能够经过setState函数修改state

总结

本文主要讲述了React组件中的数据属性-state,它是组件内部的状态,是一私有的变量,用于记录组件内部状态,因为props不可修改,经过React中内置提供setState方法修改state的值,而且定义state时,它只能是一个对象,用于存储组件内部的特殊的状态

而且大篇幅的讲到setState这个函数须要知道的,可接收两种类型的参数,一个是对象,另外一个是函数,以及这两种方式的区别,如何划分组件的状态数据,原则上是尽量的减小组件的状态。以及最后的props与state的灵魂对比

虽然能够简单的用几句话归纳props与state的做用,可是它们是很是重要的,每每程序的bug,就是经过props和state进行追踪查案的线索,是否经得起本身灵魂的拷问,我以为至今我也在摸索..

可以以props和state这种形式顺藤摸瓜,寻本溯源到页面上任何一个UI组件,这种React的能力能够说很是重要了

长路漫漫,其修远兮,待到山花烂漫时,她在丛中笑-共勉

相关文章
相关标签/搜索