之前介绍的state属性主要作用是标记组件的状态,且可以在初始化组件时就设置,而props属性主要是用来接收外部传入的数据。考虑如下诉求:实现人的信息的展示,人的信息由组件外的数据传入。此时需要使用props属性,React能自动收集组件标签中的各个属性数据,放入props属性中,然后在render方法中可以使用props属性中的数据:
class MyDemo extends React.Component{
render() {
const {name, age, sex} = this.props;
return (
<h1>
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
</h1>)
}
}
ReactDOM.render(<MyDemo name="Jack" age="18" sex="男"/>, document.getElementById("test"));
如果觉得MyDemo标签中写上各个属性很麻烦,可以自定义一个对象p,然后使用扩展运算符传入:
class MyDemo extends React.Component{
render() {
const {name, age, sex} = this.props;
return (
<h1>
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
</h1>)
}
}
let p = {name: "Jack", age: "18", sex: "男"};
ReactDOM.render(<MyDemo {...p}/>, document.getElementById("test"));
注意,在javascript中,扩展运算符...本不可以使用于对象,只能用在实现了iterator接口的对象上,但在React中能这样使用,且必须加上大括号,且只能在组件标签中这样写。但是javascript中也有大括号里使用扩展运算法作用于对象的情况,表示复制一个对象,而且是深复制,即所有属性都会重新克隆一份。React的这种大括号中使用扩展运算法作用于对象与javascript的大括号中使用扩展运算法作用于对象没有任何关系,前者只是React的jsx的一种特殊写法而已。
如果需要对传入的属性做一些约束,需要引入prop-types.js:
<script src="../static/js/prop-types.js"></script>
然后在类组件外面加上类型约束和默认值约束:
MyDemo.propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number,
sex:PropTypes.string
}
MyDemo.defaultProps = {
sex: "男",
}
这样,如果传入的名字或者性别非字符串,或者年龄非数字,则控制台会出现警告,但友好的是,依然会显示出来。如果没传性别,则为男。这些约束相当于在类上面(而非实例对象)加了propTypes和defaultProps两个属性,React会自动识别作为约束。这些约束的单词或者大小写非常容易写错,需要注意。上述约束可以简写在类里,用static关键字表示为类属性:
class MyDemo extends React.Component{
render() {
const {name, age, sex} = this.props;
return (
<h1>
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
</h1>)
}
static propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number,
sex:PropTypes.string
}
static defaultProps = {
sex: "男",
}
}
补充一下函数类型的约束:
static propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number,
sex:PropTypes.string,
fun:PropTypes.func.isRequired
}
props属性也可以在构造器中传入,但一般不这样用,详情可参见尚硅谷React技术全家桶全套完整版。props作为React组件三大属性之一,是唯一一个能被函数式组件使用的属性,state和refs都不能被函数式组件使用,上述例子使用函数式组件代码如下:
function MyDemo (props) {
const {name, age, sex} = props;
return (
<h1>
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
</h1>);
}
MyDemo.propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number,
sex:PropTypes.string,
}
MyDemo.defaultProps = {
sex: "男",
}
接下来是refs属性,refs属性主要用来取节点,代替document.getElementById类似的方法。考虑如下诉求:点击按钮,在控制台打印输入框1的内容,输入框2失去焦点,打印输入框2的内容。
class MyDemo extends React.Component{
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮打印数据"/>
<button onClick={this.showData1}>按钮</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点打印数据"/>
</div>
)
}
showData1 = () => {
console.log(this.refs.input1.value);
}
showData2 = () => {
console.log(this.refs.input2.value);
}
}
ReactDOM.render(<MyDemo/>, document.getElementById("test1"));
上述实现方式使用了字符串形式的refs,只要在标签中添加ref属性,然后就可以在this.refs中获取到相应节点,节点名ref属性值。但React官网并不推荐字符串形式的refs,以下使用函数式refs:
class MyDemo extends React.Component{
render() {
return (
<div>
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮打印数据"/>
<button onClick={this.showData1}>按钮</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点打印数据"/>
</div>
)
}
showData1 = () => {
console.log(this.input1.value);
}
showData2 = () => {
console.log(this.input2.value);
}
}
函数的入参就是当前节点,函数体就是将给当前对象挂上一个属性,名为input1,值为当前节点,因此使用时直接写this.input1.value。注意这里的回调函数是一个箭头函数,或者说匿名函数,在React初始渲染时,该函数会调用一次,如果组件更新导致React重新渲染,则该匿名函数会再调用两次,依次传入的当前节点c为null,第二次才是真正的节点,可以通过控制台打印观察。如果想避免这种小问题,可以新定义一个箭头函数:
class MyDemo extends React.Component{
render() {
return (
<div>
<input ref={this.saveInput1} type="text" placeholder="点击按钮打印数据"/>
<button onClick={this.showData1}>按钮</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点打印数据"/>
</div>
)
}
showData1 = () => {
console.log(this.input1.value);
}
showData2 = () => {
console.log(this.input2.value);
}
saveInput1 = (currentNode) => {
this.input1 = currentNode;
}
}
React官方也认为上述问题无足轻重,因此真实开发中直接用内联的箭头函数也ok。说个题外话,jsx中的注释写法也需要注意,不能直接ctrl+/,否则可能会报红(只是可能)。建议使用/* 代码 */先包起来,再用大括号{}包起来,这样注释万无一失。接下来是refs的最后一种形式:React.createRef(),这种形式不如函数的形式用得多,但也是React官网推荐的,主要思想是建议一个容器,ref属性的值都会自动存在容器中,但一个容器只能有一个ref,后来者会替换掉先来者,代码如下:
class MyDemo extends React.Component{
myRef1 = React.createRef();
myRef2 = React.createRef();
render() {
return (
<div>
<input ref={this.myRef1} type="text" placeholder="点击按钮打印数据"/>
<button onClick={this.showData1}>按钮</button>
<input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点打印数据"/>
</div>
)
}
showData1 = () => {
console.log(this.myRef1.current.value);
}
showData2 = () => {
console.log(this.myRef2.current.value);
}
}
需要注意的是,获取节点的方式时不要忘记current,这是React中的固定key的名字。