第2章 React 属性验证和默认值, refs, state

属性验证和默认值, refs, state

一.属性验证和默认值

React内置属性类型,主要依靠React.PropTypes这个静态属性,这样在书写时,可以防止属性类型写错。对应的类型为:

  • Arrays ---> React.PropTypes.array
  • Boolean ---> React.PropTypes.bool
  • Numbers ---> React.PropTypes.number
  • Strings ---> React.PropTypes.string
  • Objects ---> React.PropTypes.object
  • Functions ---> React.PropTypes.func

我们知道创建组件有3种方式,3种方式添加属性验证和默认值略有差异

1.React.createClass()

1.React.createClass()创建组件

Ⅰ.通过对象中的propTypes属性来表示默认类型

import React, { createClass, PropTypes } from "react";
    
const Summary = createClass({
    displayName: "Summary",
    // 属性验证
    propTypes: {
        ingredients: PropTypes.array,
        steps: PropTypes.array,
        title: PropTypes.string
    },
    render() {
        const {ingredients, steps, title} = this.props;
        return (
            

{title}

// 调用数组的length属性 {ingredients.length} Ingredients {steps.length} Steps

); } })

上面的属性可以知道 Summary组件的3个属性ingredients, steps为数组, title为字符串,如果传入不正确的数据类型,将会抛出错误,比如:

import { render } from "react-dom";
render(
    ,
    document.body    
);
// ingredients 和 steps 写成 字符串类型 抛出错误
// Failed propTypes: Invalid prop 'ingredients' of type 'string'  
// supplied to Constructor, expected `array`

如果不写某个属性呢?

render(
    ,
    document.body
);

由于返回的react元素中调用了数组的'length'属性,如果不提供该属性应用会抛出错误
"Uncaught TypeError: Cannot read property 'length' of undefined"

Ⅱ. React 提供了一个 isRequired 属性,其功能和html标签中'required'一致

const Summary = createClass({
    displayName: "Summary",
    // 属性验证
    propTypes: {
        // 使用 isRequired 表明该属性为必需属性
        ingredients: PropTypes.array.isRequired,
        steps: PropTypes.array.isRequired,
        title: PropTypes.string
    },
    render() {
        const {ingredients, steps, title} = this.props;
        return (
            

{title}

// 调用数组的length属性 {ingredients.length} Ingredients {steps.length} Steps

); } })

这个组件实际需要的是ingredients和steps的长度,为一个数值类型,可以将上面的改写为:

const Summary = createClass({
    displayName: "Summary",
    // 属性验证
    propTypes: {
        ingredients: PropTypes.number.isRequired,
        steps: PropTypes.isRequired,
        title: PropTypes.string
    },
    render() {
        const {ingredients, steps, title} = this.props;
        return (
            

{title}

// 将两个属性写为数值类型,不使用length {ingredients} Ingredients {steps} Steps

); } })

Ⅲ. 默认属性 Default Props

默认值是通过方法 getDefaultProps, 返回一个对象包含默认值

const Summary = createClass({
    displayName: "Summary",
    // 属性验证
    propTypes: {
        ingredients: PropTypes.number.isRequired,
        steps: PropTypes.isRequired,
        title: PropTypes.string
    },
    // 返回默认值
    getDefaultProps() {
        return {
            ingredients: 0,
            steps: 0,
            title: "[recipe]"
        }
    },
    render() {
        const {ingredients, steps, title} = this.props;
        return (
            

{title}

{ingredients} Ingredients {steps} Steps

); } })

这样即使render组件时,没有添加属性也不会报错

Ⅳ.自定义属性验证(custom property validation)

可以自定义逻辑,React通过一个函数,函数有2个参数 props, propName(when rendering component, React will inject the props object and the name of the current property into the function as the arguments),下面只写验证部分

propTypes: {
        ingredients: PropTypes.number.isRequired,
        steps: PropTypes.isRequired,
        // 自定义属性验证
        title: (props, propName) => 
            (typeof props[propName] !== "string") ?
                new Error("a title must be a string") :
                (props[propName].length > 20) ?
                    new Error("title length over 20 characters") :
                    null
    }
// 这个自定义属性验证是指: title是否为一个字符串类型,否:抛出错误,是: 验证长度是否超过20个字符
// {是: 抛出错误, 否: 不执行其他操作}

2.ES6 class创建组件

上面的内置验证默认值自定义属性验证写法有些微的差异

import React, { Componet, PropTypes} from "react";

class Summary extends Component {
    render() {
        const {ingredients, steps, title} = this.props;
        return (
            

{title}

{ingredients} Ingredients {steps} Steps

); } }

内置验证 propTypes, 默认值 defaultProps写在class外面

Summary.propTypes = {
    ingredients: PropTypes.number.isRequired,
        steps: PropTypes.isRequired,
        // 自定义属性验证
        title: (props, propName) => 
            (typeof props[propName] !== "string") ?
                new Error("a title must be a string") :
                (props[propName].length > 20) ?
                    new Error("title length over 20 characters") :
                    null
}

Summary.defaultProps = {
    ingredients: 0,
    steps: 0,
    title: "[recipe]"
}

另外一种写法通过static的写法,还在提议阶段,babel不能转换,但是以后可能成为正式规范

class Summary extends Component {
    
    static propTypes = {
        ingredients: PropTypes.number.isRequired,
        steps: PropTypes.isRequired,
        // 自定义属性验证
        title: (props, propName) => 
            (typeof props[propName] !== "string") ?
                new Error("a title must be a string") :
                (props[propName].length > 20) ?
                    new Error("title length over 20 characters") :
                    null
    }, // 注意添加逗号
    
    static defaultProps = {
        ingredients: 0,
        steps: 0,
        title: "[recipe]"
    }, // 注意添加逗号
    render() {
        const {ingredients, steps, title} = this.props;
        return (
            

{title}

{ingredients} Ingredients {steps} Steps

); } // 没有逗号 }

3.stateless functional component的写法

import {PropTypes} from "react";

// 对象解构
const Summary = ({ ingredents, steps, title }) => {
    return (
        

{title}

{ingredients} Ingredients {steps} Steps

) } // 属性验证 Summary.propTypes = { ingredients: PropTypes.number.isRequired, steps: PropTypes.number.isRequired, title: PropTypes.string } // 默认属性 Summary.defaultProps = { ingredients: 0, steps: 0, title: "[recipe]" }

另外函数参数解构可以直接添加默认值,上面可以写为

import {PropTypes} from "react";

// 对象解构
const Summary = ({ ingredents=0, steps=0, title="[recipe]" }) => {
    return (
        

{title}

{ingredients} Ingredients {steps} Steps

) } // 属性验证 Summary.propTypes = { ingredients: PropTypes.number.isRequired, steps: PropTypes.number.isRequired, title: PropTypes.string }

二.refs对象

References 或者 refs 允许React组件和子元素进行交互,最常见的一种使用情形就是: 收集用户输入,组件子元素做出相应的UI渲染
例如获取input中的值:

import React, { Component, PropTypes } from "react";

class AddColorForm extends Component {
    constructor(props) {
        super(props);
        // 绑定组件作用域
        this.submit = this.submit.bind(this);
    }
    submit(e) {
        // _title, _color分别指向1个子元素
        const { _title, _color  } = this.refs;
        e.preventDefault();
        // this.props表示组件上的属性
        this.props.onNewColor(_title.value, _color.value);
        —title.value="";
        _color.value="#000000";
        _title.focus();
    }
    render() {
        return (
            
// ref字段表示该元素
) } } // 同样 可以给组件添加属性验证和默认值 AddColorForm.propTypes = { onNewColor: PropTypes.func } AddColorForm.defaultProps = { onNewColor: f=>f // 表示一个空的函数 }

注意:

  • 通过ES6创建的组件,我们必须对需要访问this作用域的方法绑定组件的作用域
  • 通过 React.createClass()方法创建的组件,不需要给组件绑定 this 作用域,React.createClass将会自动绑定
  • 给组件想要引用的子元素添加 ref 字段

渲染时:

 {
    console.log(`todo: add new ${title} and ${color} to the list`)
}} />

无状态函数组件中使用 refs

不存在this,因此不可能使用this.refs,通过回调函数的方式设置refs,而不是字符串形式

const AddColorForm = ({onNewColor=f=>f}) => {
    let _title, _color;
    const submit = e => {
        e.preventDefault();
        onNewColor(_title.value, _color.value);
        _title.value = "";
        _color.value = "#000000";
        _title.focus();
    }
    return (
        
// ref字段表示该元素,通过回调函数返回该元素实例 _title = input} type="text" placeholder="color title..." /> _color = input} type="color" required />
) }

三.state

上面使用属性来处理数据,但是Properties are immutable,一旦渲染就不能改变, state是内置机制来操作数据,当state改变,UI重新渲染

用户与组件交互有: navigate, search, filter, select, update, delete

在React中, UI是应用状态的反射(In React, UI is a reflection of application state).

介绍组件状态

StarRating组件: 2个重要数据,Star总数,被选择的数量

1.Star组件

import React, { PropTypes } from "react";

const Star = ({ selected=false, onClick=f=>f }) =>
    
Star.propTypes = { selected: PropTypes.bool, onClick: PropTypes.func }

无状态函数组件不能使用state,但是可以成为复杂的,状态多变的组件的子元素,尽可能用无状态组件

同样我们可以通过ES6 class语法表示上面的组件

class Star extends React.Component {
    render() {
        const {selected, onClick} = this.props
        
} } Star.propTypes = { selected: PropTypes.bool, onClick: PropTypes.func } Star.defaultProps = { selected: false, onClick: f=>f }

2.StarRating组件

使用React.createClass来创建组件, 通过 getInitialState() 来初始化 state

import React, { PropTypes, createClass } from "react";

const StarRating = createClass({
    displayName: "StarRating",
    propTypes: {
        // 星星的总数
        totalStars: PropTypes.number;
    },
    getDefaultProps() {
        return {
            totalStars: 5
        }
    },
    // 获取初始状态
    getInitialState() {
        return {
            // 状态变量(state variable)初始化
            starSelected: 0
        }
    },
    change(starsSelected) {
        // 更新状态变量
        this.setState({starsSelected});
    },
    render() {
        const { totalStars } = this.props;
        const { starsSelected } = this.state; // 解构状态变量
        return (
            
// 创建长度为totalStars的数组 {[...Array(totalStars)].map((n, i) => this.change(i+1)} /> )}

{starsSelected} of {totalStars} stars

) } })

同样下面通过ES6 class 写法

this.state初始化可以直接写在构造器中

import React, {Component, PropTypes} from "react";

class StarRating extends Component {
    constructor(props) {
        super(props);
        this.state = {
            starsSelected: 0
        }
        this.onChange = this.onChange.bind(this);
    }
    change(starsSelected) {
        this.setState({starsSelected});
    }
    render() {
        const { totalStars } = this.props;
        const { starsSelected } = this.state;
        return (
            
// 创建长度为totalStars的数组 {[...Array(totalStars)].map((n, i) => this.change(i+1)} /> )}

{starsSelected} of {totalStars} stars

) } } StarRating.propTypes = { totalStars: PropTypes.number } StarRating.defaultProps = { totalStars: 5 }

3.从属性初始化状态componentWillMount()

componentWillMount() 属于 组件生命周期(Component lifecycle),多用于组件在多个应用中重用,这样允许我们直接在主键添加属性进行初始化状态:

render(
    ,
    document.body
);

1.React.createClass写法

const StarRating = React.createClass({
    //...

    // 从属性更新状态
    componentWillMount() {
        const { starsSelected } = this.state; // state variable
        if (starsSelected) {
            this.setState({ starsSelected });
        }
    },

    // ...
})

2.ES6 class 写法: 直接在构造器中写入

class StarRating extends Component {
    constructor(props) {
        super(props);
        this.state = {
            // 直接在此处添加
            starsSelected: props.starSelected || 0
        }
        this.onChange = this.onChange.bind(this);
    }
    
    // ...
}

总结

通过这章的学习应当掌握以下几点:

  1. 默认属性验证,默认值,自定义属性验证在3种创建组件方式的差异性
    • React.createClass(): 通过 propTypes对象中规定变量的React.PropTypes;通过getDefaultProps()方法返回对象来设置默认值; 通过函数(包含propspropName两个属性)来自定义验证逻辑
    • ES6 class: 通过在class外部添加属性propTypes, defaultProps分别添加属性类型及默认值,自定义属性与上面一致
    • stateless functional component: 与ES6一致,但是默认值可以通过参数对象解构的方式直接添加到参数中
    • isRequired属性表明属性为必需,这个添加到自定义属性验证中
  2. refs对象用来表示引用子元素
    • React.createClass(),ES6 class中直接在子元素中添加ref字段,字段为一个字符串类型 比如
    • stateless functional component中通过函数返回对象实例的方式来引用元素,比如 txtInput = input } />
  3. state初始化, 关键词: state variable | this.setState({stateVaribles}) | this.state
    • React.createClass()通过 getInitialState()的方法来设置state variables
    • ES6 class 直接在构造器中添加 this.state 属性来初始化状态变量
    • stateless functional components正如其名,没有状态的设置
  4. 初步接触 component lifeCycle 方法 componentWillMount()来通过组件属性来组件默认状态

你可能感兴趣的:(第2章 React 属性验证和默认值, refs, state)