日常扯淡前的废话
上一篇我们介绍了React中State对象,说到它是组件渲染的唯一依据;当然我们也可以认为State是组件中的数据源之一
,它保存着组件渲染的所有数据并且可以直接作用于
组件的渲染。这里有两个地方我们画了所谓的重点,因为要考啊。666...
Props
Props是Facebook为React组件
提供的另一个神级API。顾名思义,prop(property)就是属性的意思;props
呢就是properties,当然是很多个属性啦。因为大千世界中每个事物不可能只有一个属性,所以
处于人道主义考虑(扯淡...头伸过来),React设计师就允许我们给组件设置很多很多的属性,究竟有多多
?当然是看我们开发者自己的心情咯。组件的Props类似于DOM的attributes,随便从某个网页上inspect某个节点都能看到除了tag以外这个节点还挂载了很多别的东西,比如我们常用的class
或者id
都是节点的属性。当然React组件中Props的表现也相似。不过相对于普通DOM的属性操作,React的属性操作可以说是优雅万分,至于优雅到什么程度,下面会有例子呈上。我们一步一步来!
Props为什么能提供数据?
首先看个例子:
创建组件。一个很简单的组件,用来显示从props传来的文字
import React, {Component} from 'react';
class Show extends Component {
render() {
return (
<p>{this.props.content}</p>
)
}
}
export default Show;
使用组件
<Show content='Hello 我的!'/>
从例子中可以看到向组件Show添加一个属性很便捷,写一个属性名再赋值就行了(而如果DOM需要添加一个属性的话则需要调用setAttribute方法,异常繁琐)。其次,当我们给组件添加了属性之后,那么这些属性就被挂载到组件的this.props中
。因此这就是为什么我们可以在组件中通过this.props.content
来获取通过Props传入的文字(而如果DOM需要获取一个属性的话则需要调用getAttribute方法)。我们看下实际效果:
当然我们也可以在添加属性的同时进行简单的逻辑处理,我们改下Show组件使用方式:
<Show content={`1+1=${1 + 1}`}/>
看下实际效果:
简直神器...不过笔者在这里想强调下
不建议写复杂的逻辑。过于复杂的逻辑会降低代码的可阅读性,所以如果某个属性值被添加前需要做很多的处理,请把逻辑封装在一个方法里最后用处理结果进行赋值即可,比如酱紫:
class App extends Component {
createContent = () => {
let result = 0;
for (let i = 0; i <= 100; i++) {
result += i;
}
return result;
};
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
<h1 className="App-title">Welcome to React</h1>
</header>
<div className="App-intro">
<Show content={this.createContent()}/>
</div>
</div>
);
}
}
export default App;
这样可以将节点和逻辑分离,增加可阅读性而且代码会显得很优雅。
Props与State有什么不同?
开头有说过State并不是组件的唯一数据源,当然上面的例子也证明了这一点。然而,它们又有什么不同?
1,Props被保存在this.props
中,State被保存在this.state
中;获取也如此。
2,State可以在组件中
更新,而Props不可以。至于为什么,我们接下来会有解释;
3,State的更新会引发组件的重新渲染;Props的更新(从组件外
)并不会引发该组件的重新渲染。因此如果想在Props被更新的同时引发渲染,那么就需要在componentwillreceiveprops
中或者getDerivedStateFromProps
中将更新后的Props同步给State,这样可以触发组件的重新渲染,其本质还是借用State的力量。所以还是印证那句话:State是组件渲染的唯一依据
。
Props给我们带来了什么?
从上面的例子中我们可以看出我们可以将组件之外的数据通过Props传递给组件,这一特性也同样解决了父子组件之间通信的问题
:如果父组件想传递数据给子组件,那么就可以将所需要传递的数据通过Props传递给子组件,上面的例子写得很详细。同样,正是因为组件的Props大多都是父组件向子组件传递数据的通道,那么也就解释了为什么在子组件中不能修改Props中数据的原因,因为别人传给你的东西你改啥?出问题谁背锅?所以笔者在这里强调一个:不要在组件中更改Props!
。因为我们直接修改Props中基本类型的时候React会自动报错,但是如果修改引用类型的话比如向数组添加一个元素,React并不会报错因为地址没变进而导致组件运行异常。
Props约束
我们都知道JavaScript是一门弱类型的语言,与强类型语言比如Java不同,我们在定义一个变量的时候不会去声明这个对象到底是整型还是浮点还是字符串等等...因此当我们给组件添加属性的时候很可能会引发组件运行出错,比如组件有个属性叫list,我们希望它是一个数组并且在组件中对其使用了forEach。但是另一个开发人员引用这个组件的时候由于不清楚所以传了一个数字,那么组件就挂了。所以对组件的Props进行约束是创建一个健康组件的必要条件。
约束工具
本来React库中包含了属性监测的包,但是在某个版本被移除了(笔者记不清从哪个版本开始)。不过我们仍可通过第三方包做同样的事情 prop-types
类型约束
其实Props约束最需要解决的是类型的约束,由于篇幅有限因此下面通过一个例子介绍下具体使用方法,详情请点开链接自行查阅文档
1,首先安装第三方包是必须的npm install prop-types --save
2,在文件中引入import PropTypes from 'prop-types'
3,修改之前的例子
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class Show extends Component {
render() {
return (
<div>
<p>{this.props.content}</p>
</div>
)
}
}
Show.propTypes = {
content: PropTypes.string
};
export default Show;
我们创建完组件后并且在export之前,使用字面量对象对组件的propTypes
进行赋值以确定该组件的Props类型。这里我们约束content
属性类型为string
。假如我们传入的数据是一个数组的话
<Show content={[1, 2, 3, 4]}/>
那么运行的结果就出错了:
index.js:2178 Warning: Failed prop type: Invalid prop `content` of type `array` supplied to `Show`, expected `string`.
"必需"约束
所谓的"必需"约束是指定组件必须要某个属性,写法如下:
Show.propTypes = {
content: PropTypes.string.isRequired
};
这里表示Show组件必须要有content这个属性
。假如没有呢?
index.js:2178 Warning: Failed prop type: The prop `content` is marked as required in `Show`, but its value is `undefined`.
自然在控制台就有报错信息了。
子组件约束
子组件约束主要是约束该组件的子组件有几个,假如我们规定该组件只能有一个子组件就可以这么写:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class Show extends Component {
render() {
return (
<div>{this.props.children}</div>
)
}
}
Show.propTypes = {
children: PropTypes.element
};
export default Show;
此时我们是对该组件的
children
进行了约束,笔者认为children是React的保留字所以我们在定义Props的时候不要使用children。
使用方式:
<Show>
<p>Hello World</p>
</Show>
运行结果没有错误;
假如我们再加入一个子组件
<Show>
<p>Hello</p>
<p>World</p>
</Show>
控制台如预期报错:
Warning: Failed prop type: Invalid prop `children` of type `array` supplied to `Show`, expected a single ReactElement.
自定义约束
万物皆有其局限性。假如包提供的所有约束条件中没有我们所期望怎么办?那就自己写一个自定义约束即可;写法如下:
import React, {Component} from 'react';
class Show extends Component {
render() {
return (
<p>{this.props.custom}</p>
)
}
}
Show.propTypes = {
custom: function (props, propName, componentName) {
if (typeof(props[propName]) === 'string') {
if (!props[propName].startsWith('React')) {
return new Error(`The prop ${propName} of Component ${componentName} is not start with 'React'`)
}
} else {
return new Error(`The prop ${propName} of Component ${componentName} is not a string object`)
}
}
};
export default Show;
此时我们并不需要引入第三方库了,不过写法和前面的用法一样,只是字面量对象的值不是特定的类型而是一个校验函数
,函数的第一个参数是该组件的props集;第二个参数是被该校验函数监测的prop名字(此时是custom
;第三个参数是该组件的名字);从代码可以看出我们这个自定义约束的目的是监测该组件的custom属性值是否为字符串并且以React开头。首先我们添加正确的属性值:
<Show custom='React-Native'/>
运行正常;然后添加错误的属性值看看:
<Show custom='Native'/>
控制台打印出了我们预设的提示信息:
Warning: Failed prop type: The prop custom of Component Show is not start with 'React'
再者如果我们添加错误的类型值看看:
<Show custom={1}/>
错误信息正常打印:
Warning: Failed prop type: The prop custom of Component Show is not a string object