react state 生命周期详解 props-type Children

不能直接修改state

//错误
this.state.title='React';
正确修改方式是使用setState();
//正确
this.setState({title:'React'});

也可以使用另一个函数作为参数的setState,这个函数有两个参数,第一个参数是当前的最新状态(本次组件状态更新后的状态)的前一个状态preState(本次组件状态修改前的状态),第二个参数是当前最新的props。示列如下:

this.setState((preState,props)=>({
    counter:preState.quantity+1
}))

setState方法有两个参数  第一个参数可以是一个函数  也可以是一个对象  。第二个参数是修改了state状态的时候的回调函数

第一个参数如果是对象的话  是为了提供更加简洁的方式直接替换某个变量的值

第一个参数为函数适用于提供精确的上下文,来更新对之前状态的有依赖的操作

同时setState不保证同步的更新机制  如果在一个事件里面多次修改某一个值  为了性能会使用批量更新机制 才去队列的方式执行

setState 执行只有是在react事件里面才是异步的   其他时候都是同步的    ,

//修改msg的值是异步的
onChange = ()=>{
    this.setState({
        msg:111
    })
}
//放在定时器里面   修改msg的值就是同步的了
onChange = ()=>{
    setTimeout(function(){
         this.setState({
            msg:111
        })
    },1000)
}


function increment(prevState,props){
    return {count: prevState.count + 1};
}

function incrementMultiple(){
    this.setState(increment);
    this.setState(increment);   //函数方式
    this.setState({count: this.state.count + 1});   //对象方式
    this.setState(increment);
}

state  状态类型为不可变类型(number,string,bool,null,undefined)这种情况最简单,因为状态时不可变类型,所以直接给要修改的状态赋一个新值即可

state  状态类型为数组

//方法一:使用preState,concat创建新数组
this.setState((preState)=>({
    books:preState.books.concat(['React Guide'])
}))
//方法二:ES6 spread syntax
this.setState(preState=>({
    books:[...preState,''React Guide]
}))
当我们从books中截取部分元素作为新状态时,可以用数组的slice方法:
this.setState(preState=>({
    books:preState.books.slice(1,3);
}))
当从books中过滤部分元素后,作为新状态时,可以使用filter方法:
this.setState(preState=>({
    books:preState.books.filter(item=>{
        return item!='React';
    })
}))

注意:不要使用push,pop,shift,unshift,splice登方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改的,而concat,slice,filter会返回一个新的数组。

state  状态的类型是普通对象(不包含:string,array)

//使用es6的Object.assgin()方法
this.setState({
    onwer:Object.assgin({},preState.onwer,{name:'Jason'});
})
//使用对象扩展语法(Object spread properties):
this.setState(preState=>{
    owner:{...preState.owner,name:'Jason'}
})

replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除

默认调用setState都会重新渲染视图,但是通过shouldComponentUpdate()函数返回false来避免重新渲染。

没有导致state的值发生变化的setState是否会导致重渲染 ——【会!】

handleClick = () => {
     const preNumber = this.state.Number
     this.setState({
        Number:this.state.Number
     })
  }
 render(){
    //当render函数被调用时,打印当前的Number
    console.log(this.state.Number)
    return(<h1 onClick = {this.handleClick} style ={{margin:30}}>
             {this.state.Number}
           </h1>)
  }

上面的代码  当点击函数执行的时候  会重复的执行render函数重新渲染模板,下面是解决办法

//添加一个生名周期的钩子函数 
shouldComponentUpdate(nextProps,nextState){
      if(nextState.Number == this.state.Number){
        return false
      }
  }

PS:前后不改变state值的setState(理论上)和无数据交换的父组件的重渲染都会导致组件的重渲染,但你可以在shouldComponentUpdate这道两者必经的关口阻止这种浪费性能的行为

 

生命周期

组件触发的时候  氛围下面几个阶段

1:初始化(创建组件实例)(constructor)

2:挂载(组件实例和DOM产生关联)(componentWillMount   render   componentDidMount)

3:运行时(state,props发生变化)(componentWillReceiveProps(nextProps),

               shouldComponentUpdate(nextProps,nextState),

               componentWillUpdate(),

               render(),

               componentDidUpdate()),

4:销毁(元素失效)componentWillUnmount()

    componentWillMount(){
		console.log('componentWillMount');
	}
	
	componentDidMount(){
		console.log('componentDidMount');
	}
	
	render(){
		console.log('render');
		let loginFlag = this.state.Loginflag;
		if(loginFlag){
			return <Redirect to={{pathname:'/'}} />
		}
		return (
			<div>
				<Login_bak name={this.state.msg} />
				{loginFlag ? "111" : "222"}
				<button onClick={this.doLogin.bind(this)}>登入</button>
			</div>
		)
	}

    //加载组件的时候输出顺序 componentWillMount  render  componentDidMount  
    //也就是表示会先执行componentWillMount  再去render  最后  componentDidMount  
    //如果render函数里面有子组件    那么在执行render函数的时候   会先去加载子组件的内容,加载顺序也是一样的

 

ComponentWillMount  组件即将被调用  只执行一次

      不能操作setState修改状态

       不能操作DOM   因为还没有挂载DOM   

       和constructor  的作用差不多    不建议在该钩子中加载后台数据

ComponentDidMount   组件完成渲染  只执行一次

       可以操作setState修改状态

       可以操作DOM   

       可以在该钩子中加载后台数据

ComponentWillReceiveProps  props值更新的时候执行   根据props触发内部状态转换

       可以操作setState修改状态

       可以操作DOM 

        和当前的props进行比对  有可能什么也没有发生

shouldComponentUpdate(nextProps,nextState)  props和state更新时候触发

       不能操作setState修改状态   会导致死循环

       可以操作DOM   但是建议不要

ComponentWillUpdate  组件运行时  再次渲染前

      不能操作setState

       可以操作DOM   但是建议不要

        基本用不上该钩子函数

ComponentDidUpdate()  组件运行时  再次渲染

       可以操作setState

       可以操作DOM  

        基本用不上该钩子函数

ComponentWillUnmount()  组件销毁时执行

       不能操作setState  即使设置了也没有用

       可以操作DOM  

        基本用不上该钩子函数

 

 

关于props-type  类型检测

使用之前必须下载引入

   import PropTypes from 'prop-types'

//  propTypes能用来检测全部数据类型的变量,包括基本类型的的字符串,布尔值,数字,
以及引用类型的对象,数组,函数,甚至还有ES6新增的符号类型
static propTypes = {
     optionalArray: PropTypes.array,//检测数组类型
     optionalBool: PropTypes.bool,//检测布尔类型
     optionalFunc: PropTypes.func,//检测函数(Function类型)
     optionalNumber: PropTypes.number,//检测数字
     optionalObject: PropTypes.object,//检测对象
     optionalString: PropTypes.string,//检测字符串
     optionalSymbol: PropTypes.symbol,//ES6新增的symbol类型
}
//  propTypes类型检测的缺憾之一是,对于不确定的和无效的值,它无法捕捉错误
如果检测类型错误的话   控制台会抛出错误   但是如果传入的是undefined或者null的话   propTypes就无法检测到错误了

通过oneOfType实现多选择检测-可规定多个检测通过的数据类型

上个例子中类型检测的要求是一个变量对应一个数据类型,也就是规定的变量类型只有一个。那么怎样能让它变得灵活一些,比如规定多个可选的数据类型都为检测通过呢? PropTypes里的oneOfType方法可以做到这一点,oneOfType方法接收参数的是一个数组,数组元素是你希望检测通过的数据类型。

static propTypes = {
    number:PropTypes.oneOfType(
        [PropTypes.string,PropTypes.number]
    )
}

这时候,因为在类型检测中,号码属性的规定类型包括字符串和数字两种,所以此时控制台无报错
当然,如果你改为number = {数组或其他类型的变量},那么这时就会报错了

 通过oneOf实现多选择检测-可规定多个检测通过的变量的值

static  propTypes = {
    number:PropTypes.oneOf(
          [12,13]
      )
}

//规定props的值  必须是其中之一

arrayOf,objectOf实现多重嵌套检测

static propTypes = {
     array:PropTypes.arrayOf(PropTypes.number)
}

<Son array = {[1,2,3,4]}/>
//上面的代码是可以正常渲染的
然后我们把<Son array = {[1,2,3,4]} />改为<Son array = {['1','2','3','4']} /> ,
这样虽然也会报错但是还是可以正常渲染。报错是因为类型检测的不是非致命性错误,
不会导致页面的渲染失败

通过形状方法检测目标对象不同属性的不同数据类型

objectOf有一个缺陷,就是它内部的属性的数据类型被强行规定为一种,但通常一个对象里应该是有多种不同类型的属性了,那么这时候objectOf就不符合要求了,我们应该使用形状方法

static propTypes = {
     object:PropTypes.shape({
         name:PropTypes.string,
         age:PropTypes.number
     })
}
<Son object = {{name:'彭湖湾',age:20}}/>
//上面的代码是可以正常渲染的 
把<Son object = {{name:'彭湖湾',年龄:20}} />改成
   <Son object = {{name:'彭湖湾',年龄:'20'}} /> ,
然后就能喜闻乐见得报错了

通过isRequired检测道具中某个必要的属性(如果该属性不存在就报错)

有时候,我们在对某个变量进行类型检测时,我们不仅要求它符合预期的类型,同时也要求它是必须写入的,这时候就要用到isRequired。

static propTypes = {
          number:PropTypes.number
}
这段代码的作用是当你在道具中写入号码属性且号码属性类型错误时给予报错提示,
可如果你压根就没有写入号码属性呢?没错,什么错误都不会报。
这就是使用isRequired的必要性

修改一下
static propTypes = {
    number:PropTypes.number.isRequired
}
如果不给组件传递number参数  就会报错了

【注意】在这里给大家提个问题:我们上述的写法是数量:PropTypes.number.isRequired,
这要求数是数字类型,但如果你不想控制数的类型而仅仅是想控制它的必要性呢?\
难道写成号:isRequired或number:PropTypes.isRequired? 
这个时候PropTypes.any就登场啦!它代表了该变量可取任何一种数据类型,
所以你可以写成这样--number:PropTypes.any.isRequired

应对更复杂的类型检测 - 将PropTypes的属性值写成函数

Son.propTypes = {
     email:function(props,propName,componentName){
            if(!/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/.test(props[propName])){
                  return new Error('组件' + componentName+ '里的属性' + propName + '不符合邮箱的格式');
                         }
                }
}

<Son email = {2314838004}/>
这样就会抛出来自定义的错误了

Children

this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。

React中的Children不一定是组件,它们可以使任何东西。

JSX将会自动删除每行开头和结尾的空格,以及空行。它还会把字符串中间的空白行压缩为一个空格。这意味着以下的这些例子都会渲染出一样的情况:

<Grid>Hello world!</Grid>

<Grid>
  Hello world!
</Grid>

<Grid>
  Hello
  world!
</Grid>

<Grid>

  Hello world!
</Grid>

操作children

props.children 可以使任何的类型,比如数组、函数、对象等等。

循环Children的方式有两种    react.children  && this.props.children  分别是两种创造数组或者对方的方式

react.children  下面有多种方法   forEach  map  count   only toArray

this.props.children  如果接受的本身就是数组的话    那么就可以直接使用原生js的数组API了

当children只是一个函数的话   那么使用forEach  map的时候就会报错了   所以children最少应该有两个子组件的方式,但是确实很难知道到底有多少个子组件传过来了,所以可以使用  React.Children.count(this.props.children)  来检测子组件的个数 ,无论是什么类型 都可以检测出子组件的个数了 。记住一点不可以使用this.props.children.length来检测子组件的个数  ,因为如果子组件只有一个  而且是个字符串   这时候检测就失败了

如果以上的方法你都不适合,你能将children转换为数组通过 React.Children.toArray 方法。如果你需要对它们进行排序,这个方法是非常有用的    const children = React.Children.toArray(this.props.children)  转化了之后  就可以直接使用forEach  map方式来实现遍历操作了

执行单一child

如果你的组件,它只能在传递单一child的情况下使用,而且child必须为函数。

class Executioner extends React.Component {
  render() {
    return this.props.children()
  }
}

//如果是单一组件而且是个函数的话   那就只能这么去调用子组件了

这时候我们可以试着去强制执行 propTypes ,就像下面这样
Executioner.propTypes = {
  children: React.PropTypes.func.isRequired,
}

这会使控制台打印出一条消息,部分的开发者将会把它忽视。相反的,我们可以使用在 render 里面使用 React.Children.only
class Executioner extends React.Component {
  render() {
    return React.Children.only(this.props.children)()
  }
}

这样只会返回一个child。如果不止一个child,它就会抛出错误,让整个程序陷入中断——完美的避开了试图破坏组件的懒惰的开发者。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值