不能直接修改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,它就会抛出错误,让整个程序陷入中断——完美的避开了试图破坏组件的懒惰的开发者。