前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小结React(三):state、props、Refs

小结React(三):state、props、Refs

原创
作者头像
前端林子
修改2019-05-02 00:56:34
7.3K1
修改2019-05-02 00:56:34
举报
文章被收录于专栏:前端技术总结前端技术总结

0.引入

在React中state、props、Refs都是最基础的概念,本文将同时梳理下这三个知识点,主要内容包括:

1.state

React把每一个有状态的组件都看成是一个状态机,组件内部通过state来维护组件状态的变化。

在事件中触发setState()来修改state数据,state改变后会重新进行render()(React生命周期的内容,更多可点击

在需要对用户输入、服务器请求或者时间变化等做出响应时,使用state。

React目前支持的事件列表:

还有些不常用的事件这里没有具体列出,如有兴趣可查看

2.props

(1)React中的数据流是自上而下,从父组件流向子组件。

(2)子组件从父组件提供的props中获取数据,并进行渲染,一般是纯展示的组件。

(3)如果父组件的props更新,则该组件下面所有用到这个属性的子组件,都会重新进行render()(React生命周期的内容,更多可点击

(4)props是只读的,props是只读的,props是只读的。不要试图修改props,否则会报错。

(5)propTypes进行类型检查:

校验类型包括基本类型、对象、数组、枚举。

代码语言:javascript
复制
import PropTypes from 'prop-types';

MyComponent.propTypes = {
    // 你可以将属性声明为 JS 原生类型,默认情况下
    // 这些属性都是可选的
    optionalArray: PropTypes.array,
    optionalBool: PropTypes.bool,
    optionalFunc: PropTypes.func,
    optionalNumber: PropTypes.number,
    optionalObject: PropTypes.object,
    optionalString: PropTypes.string,
    optionalSymbol: PropTypes.symbol,

    // 任何可被渲染的元素(包括数字、字符串、元素或数组)(或 Fragment) 也包含这些类型
    optionalNode: PropTypes.node,

    // 一个 React 元素
    optionalElement: PropTypes.element,

    // 你也可以声明 prop 为类的实例,这里使用JS 的 instanceof 操作符
    optionalMessage: PropTypes.instanceOf(Message),

    // 你可以让你的 prop 只能是特定的值,指定它为枚举类型
    optionalEnum: PropTypes.oneOf(['News', 'Photos']),
};

除此之外,还可以对数组、对象类型做些比较深入的校验,如指定一个对象由特定的类型值组成。如果还不能满足需求,还可以自定义验证规则。

代码语言:javascript
复制
import PropTypes from 'prop-types';

MyComponent.propTypes = {
    // 一个对象可以是几种类型中的任意一个类型
    optionalUnion: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number,
        PropTypes.instanceOf(Message)
    ]),

    // 可以指定一个数组由某一类型的元素组成
    optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

    // 可以指定一个对象由某一类型的值组成
    optionalObjectOf: PropTypes.objectOf(PropTypes.number),

    // 可以指定一个对象由特定的类型值组成
    optionalObjectWithShape: PropTypes.shape({
        color: PropTypes.string,
        fontSize: PropTypes.number
    }),

    // 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
    // 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
    customProp: function (props, propName, componentName) {
        if (!/matchme/.test(props[propName])) {
            return new Error(
                'Invalid prop `' + propName + '` supplied to' +
                ' `' + componentName + '`. Validation failed.'
            );
        }
    },
};

如果某个属性是必须的,那么就在类型后面加上isRequired:

代码语言:javascript
复制
import PropTypes from 'prop-types';

MyComponent.propTypes = {
    // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
    // 这个 prop 没有被提供时,会打印警告信息
    requiredFunc: PropTypes.func.isRequired,

    // 任意类型的数据
    requiredAny: PropTypes.any.isRequired,
};

下面是父组件给子组件传递数据的示例:

父组件设置:

代码语言:javascript
复制
<Greeting name="Peter" />

子组件读取:

代码语言:javascript
复制
import React from 'react';
class Greeting extends React.Component {
    render() {
        return (
            <div>Hello,{this.props.name}</div>
        );
    }
}
export default Greeting;

上述就是父组件设置name属性,子组件通过this.props.name读取。那如果从父组件要传递个age属性给子组件,可以继续在父组件中设置age属性:

父组件设置:

代码语言:javascript
复制
<Greeting name="Peter" age="16"/>

子组件读取:

代码语言:javascript
复制
import React from 'react';
class Greeting extends React.Component {
    render() {
        return (
            <div>Hello,{this.props.name},age is {this.props.age}</div>
        );
    }
}
export default Greeting;

那如果还要继续传递gender、address、hobby等属性,还需要继续在父组件中一个个添加吗?

是可以写,但代码会写的比较多:

父组件设置:

代码语言:javascript
复制
const person = {
    name: "Peter",
    age: "16",
    gender: "male",
    address:"xxx",
    hobby: "coding"
}
...
<Greeting
    name={person.name}
    age={person.age}
    gender={person.gender}
    address={person.address}
    hobby={person.hobby}
/>

子组件获取:

代码语言:javascript
复制
import React from 'react';
class Greeting extends React.Component{
  let {name,age,gender,address,hobby} = this.props;
  render(){
    return (
      <div>Hello,{name},age is {age}</div>
      <div>gender:{gender}</div>
      <div>address:{address}</div>
      <div>hobby:{hobby}</div>
    );
  }
}
export default Greeting;

实际上这里在父组件设置属性时,可以用...把属性一次性地传递给子组件。避免了上述写法中手动传递多个属性,导致代码要写得很长的情况。

父组件设置:

代码语言:javascript
复制
const person = {
    name:"Peter",
    age:"16",
    gender:"male",
    address:"xxx",
    hobby: "coding"
}
...
<Greeting {...person} />

3.Refs

(1)可以用来访问在render()中的Dom节点

虽然也可以通过document.getElementById(“xxx”)来获取Dom节点,但React推荐不要直接操作Dom节点,只有用Refs才是访问组件内部Dom元素的唯一可靠方法。

(2)使用场景:

  • 控制input/video/audio,例如输入框聚焦、文本选择、媒体播放操作;
  • 触发命令式动画
  • 与第三方 DOM 库交互,比如 ECharts、地图 API

注意:不要滥用Refs。

(3)使用Refs的三种方式:

  • 字符串类型的Refs
  • 回调函数
  • React.createRef()

3.1字符串类型的Refs

这种方式是比较老的用法了,React已明确表示这种用法已经过时,并且可能会移除掉,建议用回调函数或者React.createRef()(React 16.3之后引入的)的方式来访问refs,不过还是简单看下它的用法。

它的用法是这样的:

代码语言:javascript
复制
import React from 'react';
class Greeting extends React.Component {
    constructor(props) {
        super(props);
    }
    handleClick() {
        this.refs.inputTest.focus();
    }
    render() {
        return (
            <div>
                <input type="text" ref="inputTest" />
                <input
                    type="button"
                    value="点我输入框获取焦点"
                    onClick={this.handleClick.bind(this)}
                />
            </div>
        );
    }
}
export default Greeting;

3.2回调函数

上述代码用回调函数的方式来实现:

代码语言:javascript
复制
import React from 'react';
class TextInput extends React.Component {
    constructor(props) {
        super(props);

        this.textInput = null;

        this.setTextInput = ele => {
            this.textInput = ele;
        }

        this.focusTextInput = () => {
            if (this.textInput) {
                this.textInput.focus();
            }
        }
    }

    render() {
        return (
            <div>
                <input type="text" ref={this.setTextInput} />
                <input
                    type="button"
                    value="点我输入框获取焦点"
                    onClick={this.focusTextInput}
                />
            </div>
        );
    }
}
export default TextInput;

ref传递的是一个函数:使用ref的回调函数,将text输入框的Dom节点存储到React。

3.3React.createRef()

如果是v16.3之后的React,那么可以使用这种方法。

创建Refs:

代码语言:javascript
复制
this.myRef = React.createRef();

通过ref属性来获取React元素

代码语言:javascript
复制
return <div ref={this.myRef} />;

访问Refs:

代码语言:javascript
复制
const node = this.myRef.current;

和回调函数传递一个函数不同,React.createRef()传递的是React.createRef()创建的ref属性。

上述代码用React.createRef()的方式来实现:

代码语言:javascript
复制
import React from 'react';
class TextInput extends React.Component {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
        // 通过React.createRef();来创建Refs
        this.textInput = React.createRef();
    }
    handleClick() {
        // 通过 this.textInput.current访问Dom节点
        this.textInput.current.focus();
    }
    render() {
        return (
            <div>
                <input type="text" ref={this.textInput} />
                <input
                    type="button"
                    value="点我输入框获取焦点"
                    onClick={this.handleClick}
                />
            </div>
        );
    }
}
export default TextInput;

注意:

(1)可以在类组件上使用ref属性

(2)不能在函数组件上使用ref属性,因为函数组件没有实例。如果想在函数组件上使用ref属性,那就需要转换为类组件。

(3)可以在函数组件内部可以使用ref属性,只要它指向一个 DOM 元素或者 class 组件。

代码语言:javascript
复制
// 1可以在类组件上使用ref属性
class CustomTextInput extends React.Component {
    // ...
}
class AutoFocusTextInput extends React.Component {
    constructor(props) {
        super(props);
        this.textInput = React.createRef();
    }

    componentDidMount() {
        this.textInput.current.focusTextInput();
    }

    render() {
        return (
            <CustomTextInput ref={this.textInput} />
        );
    }
}

// 2不能在函数组件上使用ref属性,因为函数组件没有实例
function MyFunctionComponent() {
    return <input />;
}

class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.textInput = React.createRef();
    }
    render() {
        // This will *not* work!
        return (
            <MyFunctionComponent ref={this.textInput} />
        );
    }
}

// 3可以在函数组件内部使用ref属性
function CustomTextInput(props) {
    // textInput must be declared here so the ref can refer to it
    let textInput = React.createRef();

    function handleClick() {
        textInput.current.focus();
    }

    return (
        <div>
            <input
                type="text"
                ref={textInput} />

            <input
                type="button"
                value="Focus the text input"
                onClick={handleClick}
            />
        </div>
    );
}

createRef比回调函数看起来更加直观,不过也没有什么压倒性优势,只是希望提供一个更便捷的方法。

3.4React.forwardRef

React 16.3还提供了一个名为React.forwardRef的API,主要是用于贯穿过父元素直接获取子元素的ref。这里的应用场景涉及HOC使用ref的问题,具体内容会在总结HOC相关内容时介绍。

4.小结

最后再概况下state、props和refs:

state:把一个有状态的组件看成是一个状态机,组件内部通过state来维护组件状态的变化。

在DOM上注册事件,触发事件时通过setState()修改了state的数据,这会导致重新render()来更新虚拟DOM,虚拟DOM再转为DOM。

props:React中的数据流就像水流一样,自上而下,从父组件流向子组件。如同下图这个水竹一样的感觉,自上而下、层层传递地流淌。

Refs:获取render()中的DOM节点。

本文主要总结了React中state、props、refs的基础知识点,如有问题,欢迎指正。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0.引入
  • 1.state
  • 2.props
  • 3.Refs
    • 3.1字符串类型的Refs
      • 3.2回调函数
        • 3.3React.createRef()
          • 3.4React.forwardRef
          • 4.小结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档