前两节课我们引入了props,从而完成了能够从外部向组件内传入属性数据,也学习了如何批量地从组件标签传入属性,并且对展开运算符做了简单的回顾。这节课我们来学习如何来限制props

回顾

在开始这街课的课程之前,我们对上节课内容做一个简单的回顾

  • props可以在标签中使用展开运算符展开一个对象来批量传入属性
  • props在使用展开运算符时必须确保被展开对象中的属性和解构props时的变量一致才能正常取数
  • 展开运算符在原生js语法中不能展开对象
  • babelreact库支持仅在组件标签批量传入属性时展开对象

新增需求

我们先看一下我们之前的代码:

class Person extends React.Component {
    render() {
        const { name, age, gender } = this.props
        return (
            <ul>
                <li>姓名:{name}</li>
                <li>性别:{gender}</li>
                <li>年龄:{age}</li>
            </ul>
        )
    }
}
const p = {name:"dsyu",age:20,gender:""}
ReactDOM.render(
    <Person name="jingxun" age="18" gender="" />, 
    document.getElementById("test"));
ReactDOM.render(
    <Person name="dschow" age="19" gender="" />, 
    document.getElementById("test1"));
ReactDOM.render(<Person {...p} />, document.getElementById("test2"))

我们上面这段代码很好理解,定义了一个类组件,在渲染组件的时候批量传入了props,然后解构props,再把数据放到合适的位置。

我们先看一下目前的效果是什么样子:

image-20211221095724272

正常展示了我们的数据。那么好,如果我们现在要求在页面上展示的年龄都是每个人的真实年龄+ 1的话,那我们应该怎么处理?可能有人要说了很简单啊,<li>年龄:{age + 1}</li>这样就可以了啊,那我们来看一下呗:

image-20211221100301909

诶哟,这是什么情况?大家回头看我们的代码,我们mock的数据中这两条传进去的都是带引号的,那么传进去的其实就是字符串。那么字符串和数字相加那就是把字符串和数字拼接起来作为了一个新的字符串,所以说才导致了这种情况。

但是第三条为啥是正确的呢?因为我们通过批量传入时mock的这条数据里面age就是一个数字类型的20所以可以正常进行数字间的运算。

然后大家有人说了,那我把上面两条的引号删掉,那我们看一下:

image-20211221101120745

报了错跟我们说我们漏写了{。我们这个18是什么?这是一个数字类型的18,但是只有在js语法中才有数据类型的概念,所以这是不是得是一个js表达式?那么就得用{}来引入,所以说<Person name="jingxun" age={18} gender="男" />这样才能正常传入数字。现在我们在看一下效果:

image-20211221101541666

这回展示结果就对了。

限制props

那么我们这样就算把新需求写完了。但是我们好像也没觉得哪里要限制啊。但是这个是我们自己在用,如果说我们在一个团队中合作,其他人来用这个组件,但是却不知道这里要传入数字类型,那就会出问题的。

所以说为了避免一些不必要的错误,我们应该来对props的属性类型来做一些相应的限制。

另外一个问题,如果我们没有传性别会怎么样<Person name="jingxun" age={18} />这样写,页面展示会是什么情况呢?

image-20211221103700422

这样的话我们会拿不到我们的性别属性。那就不展示,我们之前要求的需求是什么?我们要求如果没有指定性别就默认为男,那么这样明显不符合要求。

另外来说,如果我们的名字也不传呢?是不是页面展示上面也没有名字?我们在统计个人信息的时候就算不传性别也不传年龄,我们最后还能给手动补上,但是如果这个人只有一个年龄,没有名字你拿什么补?

所以综上所述,我们是不是有3个问题:

  • 对传入数据类型限制
  • 对传入属性是否可以为空进行限制
  • 对传入属性为空时的默认值进行限制

那么我们如何去处理呢?

我们来分析一下,我们是不是要对组件实例对象的props进行限制?那么组件实例对象是谁实例化出来的呢?是不是react?我们每写一个组件标签,react就会来实例化一个组件实例对象。那么我们是不是就干脆给组件指定一个规则,然后react在实例化组件的时候直接根据规则来限制props?接下来我们先来用一段伪代码来介绍一下大致的一个流程:

Person.属性规则 = {
  name: "非空,字符串"
  age: "数字,默认18"
  gender: "字符串,默认男"
}

但是这只是一段伪代码,我们可以看得出来我们是通过一个对象对Person组件类的props要求每个属性的传入规则。

但是代码实现怎么实现?react提供了方法:

Person.propTypes = {}

这个属性是固定的,不允许说我我不喜欢这个属性名我换一个行不行,这个真的不行。因为react每次实例化组件标签的时候都会去检查组件上也没有propTypes这个属性,如果没有,那么好,就不做任何限制,传进来啥都行,如果有,那么react就会按照这个属性来限制props属性的传入。

但是我们现在知道这个方法了,但是具体怎么写呢?

Person.propTypes = {
  name: React.PropTypes.string,
}

我们先拿name来举个例子,你看我们name是怎么限制的?我们要对name属性做类型限制,那么我们不是直接在原生js里面选一个数据类型写进来就行了,我们得找一个react认可的类型,那么好,react提供了一个对象PropTypes,大家要注意,这里的PropTypes的首字母是大写的,一定不能和Person.propTypes搞混了,因为这里是react内置的一个对象,在这个对象中提供了数据类型,我们要求name是字符串,那么就选用string类型,有人有要问了,string类型为啥首字母不要大写呢?String是原生js中的数据类型,这里不是原生js啊,这里是react内置对象中的一个属性,所以不能大写。

引入prop-types

那么好,我们现在限制了name的类型了,我们看一下效果吧。

image-20211221111421404

但是意想不到的事情出现了,页面没渲染出来,而且还报错了。说不能从undefined上面读取string,那这个报错我们见过好多次了啊,这是不是说明React.PropTypesundefined?但是我们不是说PropTypesreact的内置对象吗?因为我们的react版本问题,react 15中这个形式是没有问题的,但是react 16就弃用了。

因为如果一直在react的核心库上集成PropTypes的话,就会导致react核心库过于臃肿,而且很多时候呢,开发人员可能并不会对props进行限制,所以说PropTypes直接挂在react核心库上就不太合适了。所以从react 16开始我们就不从react核心库来取PropTypes了,那我们从那取呢?

接下来我们需要引入prop-type库,这就是专门用于限制props属性的一个库。

我们再来回想一下最一开始我们引入reactreact-dom,我们是不是说引入react全局就多了React对象?而引入react-dom全局也就多了ReactDOM对象。

那么好,我们引入了prop-types呢?也一样,全局就多了PropTypes这个对象。

现在我们在看一下还有没有报错。

image-20211221112600198

这次我页面渲染成功,也没有报错,但是好像名没有看出来是不是限制成功了,因为我们传入的属性都是符合限制条件的。如果我把传入的名字改成数字呢?<Person name={1222} age={18} gender="男"/>这样的话页面会是什么样子呢?

image-20211221112908664

看一下,我们页面虽然渲染出来了,但是是不是报错了?告诉我们名字属性期待的是一个字符串,却接收到了一个数字。所以说这样就可以避免出现这种数据上的错误。

但是我们类型设置了,可是我们name必须非空还没限制呢啊,这个怎么写啊?要再写一行吗?但是在写一行的话对象里面不允许出现重复的key啊,其实不用。后面加一步PropTypes.string.isRequired就可以实现,现在我们看一下我们如果不传name会是什么效果。

image-20211221114218413

是不是提醒我们说name被标记为必须项,但是却接收到了undefined。所以这样我们就可以来提醒使用组件的开发者某个属性是必须的了。

好了名字的限制我们已经写好了,那么再来限制一下性别,我们的需求是什么啊?性别得是字符串,可以不传吗?可以的,但是如果不传的话要指定一个默认值。怎么这个我们这么写呢?限制类型我们会了,但是默认值应该怎么设置?

react中其实也为我们提前设计好了。我们有一个defaultProps对象,那么还是那个问题,这个对象我应该给谁来指定啊?是不是还是给Person指定?,那么好,我们来吧默认值也给加上看一下代码:

Person.propTypes = {
  name: PropTypes.string.isRequired,
  gender: PropTypes.string,
}
Person.defaultProps = {
  gender: "男"
}

这样我们是不是就完成了gender属性的限制呢?那我们看一下啊效果就知道了啊,我们在渲染组件标签的时候不穿性别:<Person name="jingxun" age={18} />

image-20211221131438691

我们从图中看出,页面正常展示,性别为男,而且也没有报错。那么这样就完成了对gender的限制。

那么年龄呢?我们要求是数字类型,而且也可以不传,但是要有一个默认值是18,那就类比一下呗,字符串类型我们是PropTypes.string那么数字类型是不是就是PropTypes.number呢?我们来看一下:渲染组件标签<Person name="jingxun" />我们先不传age看一下结果

image-20211221131932440

这个年龄变成了NAN,这是为什么?我们是不是在组件里面写了{age + 1},我们在组件标签有没有传age?没传age,而且我还没有写默认值,那我拿到的是不是undefined?那么undefined与数字运算得到的结果就是NAN,因为这个结果确实就不是数字。

那么我们再渲染<Person name="jingxun" age="18" />

image-20211221132352571

结果有错误警告,跟我们说期待的是number但是传入了字符串,这说明我们的限制有效,但是我们只是能证明我们的限制有效,但是怎么证明我们写的number就是对的呢?那我们传个数字进去看看报不报错不就醒了嘛,我们来看一下:

image-20211221132631881

这次没有错误警告,说明我们确实是使用number来限制数字类型的。这个问题解决了,那么我们的默认值其实也就很简单了,和gender的默认值一样的写法啊:

Person.propTypes = {
  name: PropTypes.string.isRequired,
  gender: PropTypes.string,
  age: PropTypes.number
}
Person.defaultProps = {
  gender: "男",
  age:18
}

那么我们现在不传age在来试一下<Person name="jingxun" />,这里我性别和年龄都没用传,看看页面是什么样子:

image-20211221133042022

这一次页面展示也正常也没有错误警告所以说以上便完成了相关的限制

补充

以上其实我们已经完成了对props的限制,实际上呢props也并不是必须要限制的,但是限制一下的话可以对规范性更好,也可以帮助我们避免一些错误。但是如果我要传一个函数到组件里的话怎么限制呢?传的话很简单直接按照标签属性来传就行,但是我怎么限制呢?比如我这样:

class Person extends React.Component {
  render() {
    const { name, age, gender } = this.props;
    this.props.speak();
    return ...
  }
}
function speak() {console.log("speak");}
ReactDOM.render(
  <Person name="jingxun" speak={speak} />, 
  document.getElementById("test"));

这样的话我肯定可以传进去,我们看一下效果:

image-20211221134025000

是不是成功传入了?控制台成功打印出了speak,但是我要是想限制speak属性必须传入一个函数应该怎么写?是不是PropTypes.function?看着好像应该是这样,我们来看一下:

Person.propTypes = {
  ...
  speak: PropTypes.function
}

我们在Person.propTypes里对speak做了限制,要求类型必须是function,那我们看一下效果:

image-20211221134516760

有一个错误警告说:我们对speak的限制不被允许,因为必须是一个函数,但是prop-types库里面取到的类型是undefined,这是什么意思啊?怎么感觉有点没看懂。这句话是什么意思呢,就是我们在 限制speak的时候从prop-types库中没取到PropTypes.function,当一个对象取一个属性没取到那么会得到什么啊?那肯定是undefined

所以我们得到一个结论就是,对函数类型的限制不是PropTypes.function。那应该是什么呢?我们回想一下字符串类型在js里面是String,到了这里成了小写的string。数字类型在js里面是Number,但是到了这里去变成了小写的number,这种设计就是为了避免与js冲突。那么有没有可能反其道而行之,函数到了这里要把function首字大写呢?那我们试一下呗:

Person.propTypes = {
  ...
  speak: PropTypes.Function
}

现在再看一下效果:

image-20211221135501295

依然不对。到这里很多人要开始说了,那我这怎么办,我写不下去了,我干脆就不做限制了。当然,允许。可是我这里还是要把问题解决掉的。

prop-types在设计的时候,也别说prop-types库了,我们任何代码在设计的时候是不是都要讲究统一性啊?既然stringnumber都小写了,那我function首字母大写,虽然也避免了与js冲突,但是在设计哲学上是不是有些突兀了?不符合统一性原则。所以这里function首字母不大写,但是不大写,这是个关键字啊,我们还是会和js冲突的,没有关系,我们换一种写法,我们不写function,我们写func。这样不就不冲突了嘛,而且还符合统一性原则。我们在来看一下:

Person.propTypes = {
  ...
  speak: PropTypes.func
}

image-20211221140209684

这一次就没有错误警告了。

以上便是对props的限制

总结

  • 限制并不是必须的
  • 限制props必须引入prop-types
  • 限制可以避免一些不必要的错误
  • 限制有三种场景
  • 限制属性是否必须非空
  • 限制属性类型
  • 限制属性默认值
  • 限制属性类型为函数时,不能用function而要用func

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/limit_props/