组件

作者: 珍珠林 | 来源:发表于2017-05-18 11:55 被阅读0次

    组件是React的基石,所有的React应用程序都是基于组件的。
    React组件,可以通过React.createClass来声明:

    var List = React.createClass({
        getInitialState: function() {
            return ['a', 'b', 'c']
        },
        render: function() {
            return (...);
        }
    });
    

    建议全部使用ES6写法,React官方也在第一时间就支持了ES6 class的写法:

    import React from 'react';
    
    class List extends React.Component {
        constructor() {
            super();
            this.state = ['a', 'b', 'c'];
        }
        render() {
            return (...);
        }
    }
    

    porps属性

    Profile.jsx组件
    props就是传入组件的属性,由外部JSX传入,在组件内部通过this.props访问

    import React from 'react';
    
    export default class Profile extends React.Component {
      // 渲染一个Vitrual DOM结构
      render() {
        return (
          <div className="profile-component">
            <h1>我的名字叫{this.props.name}</h1> 
            <h2>我今年{this.props.age}岁</h2>
          </div>
        )
      }
    }
    

    把组件挂载到DOM节点上

    import { render } from 'react-dom';
    import Profile from './profile';
    
    render(<Profile name="viking" age=20 />, document.getElementById('container'));
    

    或者使用...属性扩展

    const props = {
      name: 'viking',
      age: 20
    };
    render(<Profile {...props} />, document.getElementById('container'));
    

    React可以让用户定义组件属性的变量类型

    import { PropTypes } from 'react';
    
    const propTypes = {
      // 验证不同类型的JavaScript变量
      optionalArray: PropTypes.array,
      optionalBool: PropTypes.bool,
      optionalFunc: PropTypes.func,
      optionalNumber: PropTypes.number,
      optionalObject: PropTypes.object,
      optionalString: PropTypes.string,
      
      // 可以是一个ReactElement类型
      optinalElement: PropTypes.element,
    
      // 可以是别的组件的实例
      optinalMessage: PropTypes.instanceOf(Message),
    
      // 可以规定为一组值其中的一个
      optinalEnum: PropTypes.oneOf(['News', 'Photos']),
    
      // 可以规定是一组类型中的一个
      optionalUnion: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number,
        PropTypes.instanceOf(Message)
      ]),
      // 可以在最后加一个isRequired, 表明这个属性是必需的,否则就会返回一个错误
      requiredFunc: React.PropTypes.func.isRequired
    }
    

    给刚才的组件添加验证

    import React, { PropTypes } from 'react';
    // 需要验证属性
    const propTypes = {
      name: PropTypes.string.isRequired,
      age: PropTypes.number.isRequired
    };
    
    class Profile extends React.Component {
      // render是这个组件渲染的Vitrual DOM结构
      render() {
        return (
          <div className="profile-component">
            <h1>我的名字叫{this.props.name}</h1>
            <h2>我今年{this.props.age}岁</h2>
          </div>
        )
      }
    }
    
    // 将验证赋值给这个组件的propTypes属性
    Profile.propTypes = propTypes;
    
    export default Profile;
    

    state状态

    state是组件内部属性。组件本身是一个状态机,它可以在constructor中通过this.state直接定义它的值,然后根据这些值来渲染不同的UI。当state值发生改变时,可以通过this.setState方法让组件再次调用render方法,来渲染新的UI。
    改造一下上面的组件,添加一个点赞按钮,每单机一次,就给赞的次数加1:

    // Profile.jsx
    export default class Profile extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          liked: 0
        };
        // ES6 class的组件声明方式,不会把自定义的函数绑定到实例上,需要手动绑定
        this.likedCallback = this.likedCallback.bind(this);
      }
    }
    
    likedCallback() {
      let liked = this.state.liked;
      liked++;
      this.setState({
        liked
      });
    }
    
    render() {
      return (
        <div>
          <h1>我的名字叫{this.props.name}</h1>
          <h2>我今年{this.props.age}岁</h2>
          <button onClick="this.likedCallback">给我点赞</button>
          <h2>总点赞数:{this.state.liked}</h2>
        </div>
      )
    }
    

    组件生命周期

    组件加载

    • getDefaultProps:在组件中的赋值的数据会被设置到this.props中
    • getInitialState:返回值会被设置到this.state中
    getInitialState: function() {
        return {
          count: 0
        };
    }
    

    ES6的写法,只需写在constructor中即可

    constructor(props) {
        super(props);
        // 这里声明state
        this.state = {count: 0};
    }
    
    • componentWillMount:可以在渲染之前做一些准备工作。
    • render:必要方法。返回一个ReactElement对象。render是一个纯函数,不应该有任何修改组件state或者和浏览器交互的情况。
    • componentDidMount:render之后调用,从这里开始获取组件的DOM结构。如果想让组件加载完毕后做一些额外操作(比如Ajax请求等),可以在这里进行。

    组件props更新

    • componentWillReceiveProps(object nextProps):在组件接收到新的props的时候触发,参数nextProps就是传入的新的props,可以用它来和this.props比较,来决定是否用this.setState实现UI重新渲染。
    • shouldComponentUpdate:重新render之前被调用,可以返回一个布尔值来决定组件是否要更新,默认返回true
    • componentWillUpdate:render之前被调用,可以在渲染之前做一些准备工作,和componentWillMount类似。
    • render:同首次加载的render
    • componentDidUpdate:render完成后调用,和componentDidMount类似。

    组件卸载

    • componentWillUnmount:在组件被卸载之前调用,可以在之类做一些清理工作

    组合组件

    一个组件可以包含多个其他组件,继续扩展一下上面的应用,显示一个爱好列表

    // Hobby.jsx 爱好组件
    import React, { PropTypes } from 'react';
    
    const propTypes = {
      hobby: PropTypes.string.isRequired
    };
    
    class Hobby extends React.Component {
      render() {
        return <li>{this.props.hobby}</li>
      }
    }
    
    Hobby.propTypes = propTypes;
    
    export default Hobby;
    
    // Profile.jsx 使用爱好的组合组件
    import Hobby from './hobby';
    ...
    constructor(props) {
      super(props);
      this.state = {
        liked: 0,
        hobbies: ['skateboarding', 'rock music']
      };
      ...
      render() {
        return (
          <div>
            <h1>我的名字叫{this.props.name}</h1>
            <h2>我今年{this.props.age}岁</h2>
            <button onClick="{this.likedCallback}">给我点赞</button>
            <h2>总点赞数:{this.state.liked}</h2>
            <h2>我的爱好:</h2>
            <ul>
              {this.state.hobbies.map((hobby, i) => <Hobby key={i} hobby={hobby} />)}
            </ul>
          </div>
        )
      }
    }
    

    只要将子组件看成自定义HTML标签就好了,传入想要的属性。特别注意要给每个循环组件添加一个唯一的key值。


    无状态函数式组件

    Hobby这类组件,没有内部state,不需要组件生命周期函数。可以用纯函数的形式来表达。它做的事情只是根据输入来生成组件,没有其他副作用

    // 无状态函数
    function Hobby(props) {
      return <li>{props.hobby}</li>
    }
    

    state设计原则

    创建尽量多的无状态组件,这些组件唯一关心的就是渲染数据。而在最外层,应该有一个包含state的父级别组件,用于处理各种事件、交流逻辑、修改state。对应的子组件要关心的只是传入的属性而已。
      state应该包含组件的事件回调函数可能引发UI更新的这类数据。在实际的项目中,应该是轻量化的JSON数据,尽量把数据的表现设计到最小,更多的数据可以在render中通过各种计算得到。
    比如,有一个商品列表和用户已选购的商品列表,最直观的state设计如下:

    {
      goods: [
        {
          "id": 1,
          "name": "paper"
        },
        {
          "id": 2,
          "name": "pencil"
        },
        ...
      ],
      selectedGoods: [
        {
          "id": 1,
          "title": "hello world"
        }
      ]
    }
    

    这样做当然可以,但是根据最小化设计state原则,selectedGoods的商品就是goods里的几项。所以可以修改成:

    selectedGoods: [1, 2, 3]
    

    渲染时,只要把渲染的条目从goods中取出来就可以了。
    state不应该包含哪些数据?为达到state最小化设计,下面这几种数据不应该包含在state中:

    1. 可以由state计算得到的数据。就像刚才的selectedGoods一样
    2. 组件。组件不需要保存到state中
    3. props中的数据。props可以看作是数据来源,它不需要保存在state中。

    DOM操作

    大多数情况下,不需要操作DOM去更新UI,应使用setState。但是有些情况确实需要访问一些DOM(如表达的值),那么可采用refs方式来获得DOM节点。只需要加个ref属性,然后通过this.refs.name来获得对应的DOM结构。

    // Profile.jsx
    render() {
      return (
        <div>
          ...
          <input type="text" ref="hobby" />
          <button onClick={this.addHobbyCallback}>添加爱好</button>
        </div>
      )
    }
    

    在button上添加事件,取得input的值,添加到state的值里面:

    // Profile.jsx
    addHobbyCallback() {
      // 用this.refs.name来取得DOM节点
      let hobbyInput = this.refs.hobby;
      let val = hobbyInput.value;
      if (val) {
        let hobbies = this.state.hobbies;
        // 添加值到数组
        hobbies = [...hobbies, val];
        // 更新state, 刷新UI
        this.setState({
          hobbies
        }, () => {
          hobbyInput.value = '';
        });
      }
    }
    

    相关文章

      网友评论

          本文标题:组件

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