美文网首页
React组件实例的三大核心属性:state、props、ref

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

作者: 梁帆 | 来源:发表于2022-08-11 11:33 被阅读0次

    前言

    在我们创建 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,所以像这种

    相关文章

      网友评论

          本文标题:React组件实例的三大核心属性:state、props、ref

          本文链接:https://www.haomeiwen.com/subject/hstzwrtx.html