React组件实例的三大核心属性:state、props、refs

前言

在我们创建 React.Component 类的实例对象时,可以试着输出下 this

class Weather extends React.Component {
    render() {
        console.log(this)
        return <h1>今天天气很炎热</h1>
    }
}

在浏览器控制台中,可以看到输出了如下结构:


对象实例结构图

一、state

场景需求:当我们点击页面上的“今天天气很炎热”时,页面内容会切换成“今天天气很凉爽”,反之亦可。

state 是组件对象最重要的属性,从上图可以看到state默认是undefined,而state值需要被初始化成一个Object对象。下面,我们将在Weather组件的构造函数中,传参并且赋值给state。
这里我们的需求是控制炎热/凉爽的交替输出,需要给 state 初始化,注意要用对象实例来初始化:

class Weather extends React.Component {
    constructor(props) {
        super(props)
        this.state = {isHot: true}
    }
    render() {
        console.log(this)
        const { isHot } = this.state
        return <h1>今天天气很{isHot ? "炎热" : "凉爽"}</h1>
    }
}

再打开console,可以看到,我们已经成功把表达天气严不严热的参数赋值给了state:
对象赋值给state

state初始化完成后,就是给标签添加click事件:

class Weather extends React.Component {
    constructor(props) {
        super(props)
        // 初始化状态
        this.state = { isHot: true }
        this.changeWeather = this.changeWeather.bind(this)
    }
    render() {
        // 读取状态
        const { isHot } = this.state
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? "炎热" : "凉爽"}</h1>
    }
    changeWeather() {
        console.log(this)
        this.setState({isHot: !this.state.isHot})
    }
}

这个示例中,我们为了解决changeWeather的this指向性问题,使用了

this.changeWeather = this.changeWeather.bind(this)

这行代码,这层绑定后,标签里onClick后的函数方法就能正常运行。但是这样会带来新的问题,就是当类方法多的时候,就得手动添加很多的更改this指向的代码。
所以我们做如下简化:

class Weather extends React.Component {
    // (1)初始化状态
    state = { isHot: true }

    render() {
        const { isHot } = this.state
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? "炎热" : "凉爽"}</h1>
    }

    // (2)自定义方法————要用赋值语句和箭头函数
    changeWeather = () => {
        console.log(this)
        this.setState({isHot: !this.state.isHot})
    }
}

首先我们直接把构造函数的state提取出来,其次根据箭头函数的特征——没有自己的this,需要this时要往外找——即改成(2)中这样的自定义方法,其中的this输出的就是一个Weather的实例对象,打开看可以发现:

更改后的Weather实例对象
changeWeather方法是直接在Weather中的,而不在原型对象里,做个对比,我们输出代码更改前的Weather实例对象:
更改前的Weather实例对象
这里的changeWeather是在原型对象里的。

此外还需要注意一点,就是state数据,是不能直接修改或更新的,必须借助this.setState({...})来做。

二、props

场景需求:自定义用来显示一个人员信息的组件。
(1)姓名必须指定,且为字符串类型
(2)性别为字符串类型,如果没有制定,默认为"男"
(3)年龄必须指定,且为数字类型

我们在render时,可以给组件指定data,然后在Person类的render方法中,用this.props读取出来:

<script type="text/babel">
    // 1.创建类式组件(适用于复杂组件)
    class Person extends React.Component {
        render() {
            console.log(this)
            const { name, age, gender } = this.props
            return (
                <ul>
                    <li>姓名: {name}</li>
                    <li>性别: {age}</li>
                    <li>年龄: {gender}</li>
                </ul>
            )
        }
    }
    // 2.渲染组件到页面
    const data = {
        name: "Tom",
        age: 18,
        gender: "male"
    }
    ReactDOM.render(<Person {...data}/>, document.getElementById("test"))
</script>

我们在render()中输出this对象,可以看到props被初始化了。

Person对象中的props

按照题设要求,我们需要对props类型进行限制,已经设定默认值。这我们主要用到Person.propTypesPerson.defaultProps,具体的设置过程如下所示:

<script type="text/babel">
    class Person extends React.Component {
        render() {
            console.log(this)
            const { name, age, gender } = this.props
            return (
                <ul>
                    <li>姓名: {name}</li>
                    <li>性别: {age}</li>
                    <li>年龄: {gender}</li>
                </ul>
            )
        }
    }
    // 对标签属性进行类型、必要性的限制
    Person.propTypes = {
        name: PropTypes.string.isRequired,  // 限制name为string,且为必传
        age: PropTypes.number,  // 限制age为number
        gender: PropTypes.string,   // 限制gender为string
        speak: PropTypes.func  // 限制speak为func
    }
    // 给标签属性设定默认值
    Person.defaultProps = {
        gender: "unknown",  // 设置gender默认值为 unknown
        age: 0 //设置age默认值为 0 
    }
    const data = {
        name: "Tom",
        age: 18,
        gender: "male"
    }
    ReactDOM.render(<Person {...data}/>, document.getElementById("test"))
</script>

这段代码我们可以发现,Person.propTypesPerson.defaultProps类方法的写法,因此可以写到Person的里面,加上static关键字:

class Person extends React.Component {
    // 对标签属性进行类型、必要性的限制
    static propTypes = {
        name: PropTypes.string.isRequired,  // 限制name为string,且为必传
        age: PropTypes.number,  // 限制age为number
        gender: PropTypes.string,   // 限制gender为string
        speak: PropTypes.func  // 限制speak为func
    }
    // 给标签属性设定默认值
    static defaultProps = {
        gender: "unknown",  // 设置gender默认值为 unknown
        age: 0 //设置age默认值为 0 
    }
    render() {
        console.log(this)
        const { name, age, gender } = this.props
        return (
            <ul>
                <li>姓名: {name}</li>
                <li>性别: {age}</li>
                <li>年龄: {gender}</li>
            </ul>
        )
    }
}

在上述代码中,我们都省略了构造函数,但是其实构造函数也是可以接受props的:

class Person extends React.Component {
    constructor(props) {
        super(props)
        console.log(this.props)
    }
    ...
}

这样我们可以正常接收到props的值:


constructor正常输出props

但如果我们不指定props参数,如下:

constructor() {
  console.log(this.props)
}
//或者
constructor(props) {
  super()
  console.log(this.props)
}

这样都会出错。

React官方文档对于constructor的注解

我们可以得出如下结论:
constructor是否接收props,是否要传递给super,主要取决于:是否希望在constructor中通过this访问props。
但其实这种场景几乎不会出现,稍作注意就好。

函数式组件使用props

函数参数默认传了props,如下:

<script type="text/babel">
    function Person(props) {
        console.log(props)
        return (
            <ul>
                <li>姓名: {props.name}</li>
                <li>性别: {props.age}</li>
                <li>年龄: {props.gender}</li>
            </ul>
        )
    }
    const data = {
        name: "Tom",
        age: 18,
        gender: "male"
    }
    ReactDOM.render(<Person {...data} />, document.getElementById("test"))
</script>

如果想指定props中的类型,那跟之前写类方法时类似的:

Person.propTypes = {
    name: PropTypes.string.isRequired,  // 限制name为string,且为必传
    age: PropTypes.number,  // 限制age为number
    gender: PropTypes.string,   // 限制gender为string
    speak: PropTypes.func  // 限制speak为func
}
Person.defaultProps = {
    gender: "unknown",  // 设置gender默认值为 unknown
    age: 0 //设置age默认值为 0 
}

三、refs

场景需求:场景中有两个输入框和一个按钮,当我们点击button时,页面alert输入框1中的内容;当我们在输入框2中输入内容,此时点击页面空白处使得输入框失去焦点,页面alert输入框2中的内容。

1.字符串形式的ref(待废弃)

为满足场景需求,我们写了如下代码:

<script type="text/babel">
    class Demo extends React.Component {
        showData = () => {
            const input1 = document.getElementById("input1")
            alert(input1.value)
        }
        render() {
            return (
                <div>
                    <input id="input1" type="text" placeholder="click button..." />&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input type="text" placeholder="lose focus..." />
                </div>
            )
        }
    }

    ReactDOM.render(<Demo />, document.getElementById("test"))
</script>

如图所示:

输出结果
我们再输出this的值:
this的值

接下来,我们要用到ref关键字,将代码改成:

<script type="text/babel">
    class Demo extends React.Component {
        showData = () => {
            alert(this.refs.input1.value)
        }
        showData2 = () => {
            alert(this.refs.input2.value)
        }
        render() {
            console.log(this)
            return (
                <div>
                    <input ref="input1" type="text" placeholder="click button..." />&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input ref="input2" onBlur={this.showData2} type="text" placeholder="lose focus..." />
                </div>
            )
        }
    }

    ReactDOM.render(<Demo />, document.getElementById("test"))
</script>

我们首先把input1的id改成了ref关键字,然后我们打印出this

this中的refs

可以看到其中的refs属性已经有了input1,这样我们只需要用this.refs.input1来获取即可,这样也实现了跟上述用id的复杂写法一样的目的。
以上,是 字符串形式的ref。React官方文档中是这么写的:
react文档对String类型的refs的说明

简单来讲,这个写法存在效率问题,最好别用。

2.回调形式的ref

要用回调形式的ref,就如下面代码所示:

<script type="text/babel">
    class Demo extends React.Component {
        showData = () => {
            alert(this.input1.value)
        }
        showData2 = () => {
            alert(this.input2.value)
        }
        render() {
            console.log(this)
            return (
                <div>
                    <input ref={ c => this.input1 = c} type="text" placeholder="click button..." />&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input ref={ c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="lose focus..." />
                </div>
            )
        }
    }

    ReactDOM.render(<Demo />, document.getElementById("test"))
</script>

关于回调refs的说明
所谓的内联函数,就跟我们上面写的示例一样,它是直接在ref中把函数体写进去的。在文档中,它写了特指更新过程会被执行两次,一次是null,一次是当前节点,由于我们例子中不涉及更新过程,体现不出创建两次的问题。不过还是用这个例子来演示解决方法,即不用内联函数,要用绑定函数,代码如下:

<script type="text/babel">
    class Demo extends React.Component {
        showData = () => {
            alert(this.input1.value)
        }
        showData2 = () => {
            alert(this.input2.value)
        }
        saveInput1 = (curNode) => {
            this.input1 = curNode
        }
        saveInput2 = (curNode) => {
            this.input2 = curNode
        }
        render() {
            console.log(this)
            return (
                <div>
                    <input ref={this.saveInput1} type="text" placeholder="click button..." />&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input ref={this.saveInput2} onBlur={this.showData2} type="text" placeholder="lose focus..." />
                </div>
            )
        }
    }

    ReactDOM.render(<Demo />, document.getElementById("test"))
</script>

这样也是正常的,而且ref函数不会被执行两次。

3.createRef()

React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,且一个容器只能存储一个值。
写法如下:

<script type="text/babel">
    class Demo extends React.Component {
        input1 = React.createRef()
        input2 = React.createRef()
        showData = () => {
            alert(this.input1.current.value)
        }
        showData2 = () => {
            alert(this.input2.current.value)
        }
        render() {
            console.log(this)
            return (
                <div>
                    <input ref={this.input1} type="text" placeholder="click button..." />&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input ref={this.input2} onBlur={this.showData2} type="text" placeholder="lose focus..." />
                </div>
            )
        }
    }

    ReactDOM.render(<Demo />, document.getElementById("test"))
</script>

4.可以不使用ref

            showData2 = (event) => {
                alert(event.target.value)
            }
            render() {
                console.log(this)
                return (
                    <div>
                        <input ref={this.input1} type="text" placeholder="click button..." />&nbsp;
                        <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                        <input onBlur={this.showData2} type="text" placeholder="lose focus..." />
                    </div>
                )
            }

只需要把节点的ref去掉,并且在showData2中传入event,值我们用event.target.value获取。
文档中提示:不要过度使用ref,所以像这种

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容