第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 (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    // 调用数组的length属性
                    <span>{ingredients.length} Ingredients</span>
                    <span>{steps.length} Steps</span>
                </p>
            </div>
        );
    }
}) 

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

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

如果不写某个属性呢?

render(
    <Summary />,
    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 (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    // 调用数组的length属性
                    <span>{ingredients.length} Ingredients</span>
                    <span>{steps.length} Steps</span>
                </p>
            </div>
        );
    }
}) 

这个组件实际需要的是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 (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    // 将两个属性写为数值类型,不使用length
                    <span>{ingredients} Ingredients</span>
                    <span>{steps} Steps</span>
                </p>
            </div>
        );
    }
}) 

Ⅲ. 默认属性 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 (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    <span>{ingredients} Ingredients</span>
                    <span>{steps} Steps</span>
                </p>
            </div>
        );
    }
})

这样即使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 (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    <span>{ingredients} Ingredients</span>
                    <span>{steps} Steps</span>
                </p>
            </div>
        );
    }
}

内置验证 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 (
            <div className="summary">
                <h1>{title}</h1>
                <p>
                    <span>{ingredients} Ingredients</span>
                    <span>{steps} Steps</span>
                </p>
            </div>
        );
    } // 没有逗号
}

3.stateless functional component的写法

import {PropTypes} from "react";

// 对象解构
const Summary = ({ ingredents, steps, title }) => {
    return (
        <div className="summary">
            <h1>{title}</h1>
            <p>
                <span>{ingredients} Ingredients</span>
                <span>{steps} Steps</span>
            </p>
        </div>
    )
}
// 属性验证
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 (
        <div className="summary">
            <h1>{title}</h1>
            <p>
                <span>{ingredients} Ingredients</span>
                <span>{steps} Steps</span>
            </p>
        </div>
    )
}
// 属性验证
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 (
            <form onSubmit={this.submit}>
                // ref字段表示该元素    
                <input 
                    ref="_title"
                    type="text"
                    placeholder="color title..."
                />
                <input 
                    ref="_color"
                    type="color"
                    required
                />
                <button>ADD</button>
            </form>
        )
    }
}
// 同样 可以给组件添加属性验证和默认值
AddColorForm.propTypes = {
    onNewColor: PropTypes.func
}
AddColorForm.defaultProps = {
    onNewColor: f=>f  // 表示一个空的函数
}

注意:

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

渲染时:

<AddColorForm onNewColor={(title, color) => {
    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 (
        <form onSubmit={submit}>
                // ref字段表示该元素,通过回调函数返回该元素实例    
                <input 
                    ref={input => _title = input}
                    type="text"
                    placeholder="color title..."
                />
                <input 
                    ref={input => _color = input}
                    type="color"
                    required
                />
                <button>ADD</button>
            </form>
    )
}

三.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 }) =>
    <div 
        className={(selected) ? "star selected" : "star"}
        onClick={onClick}
    >
    </div>

Star.propTypes = {
    selected: PropTypes.bool,
    onClick: PropTypes.func
}

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

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

class Star extends React.Component {
    render() {
        const {selected, onClick} = this.props
        <div 
            // 选中则添加selected类
            className={(selected) ? "star selected" : "star"}
            onClick={onClick}
        >
        </div>
    }
}
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 (
            <div className="star-rating">
                // 创建长度为totalStars的数组
                {[...Array(totalStars)].map((n, i) =>
                    <Star
                        key={i}
                        // 注意这段逻辑
                        selected={i<starsSelected}
                        onClick={()=>this.change(i+1)}
                    />    
                )}
                <p>{starsSelected} of {totalStars} stars</p>
            </div>
        )
    }
})

同样下面通过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 (
            <div className="star-rating">
                // 创建长度为totalStars的数组
                {[...Array(totalStars)].map((n, i) =>
                    <Star
                        key={i}
                        // 注意这段逻辑
                        selected={i<starsSelected}
                        onClick={()=>this.change(i+1)}
                    />    
                )}
                <p>{starsSelected} of {totalStars} stars</p>
            </div>
        )
    }
}
StarRating.propTypes = {
    totalStars: PropTypes.number
}
StarRating.defaultProps = {
    totalStars: 5
}

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

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

render(
    <StarRating 
        totalStars={7}
        starsSelected={3}
    />,
    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字段,字段为一个字符串类型 比如 <input ref="txtInput" />
    • stateless functional component中通过函数返回对象实例的方式来引用元素,比如<input ref={input => 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()来通过组件属性来组件默认状态
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容