组件

元素是构建 React 应用的描述界面的最小单位,在作用上,我们可以把 React 元素理解为DOM元素,但实际中,React 元素只是js中普通的对象。在 React 内部有一套React DOM的机制,我们可以称之为 Virtual DOM,通过js中树状结构的对象来模拟真实 DOM。

React 之所以轻和快就是因为这虚拟DOM的存在,React 内部还实行了一个低复杂度高效率的 Diff 算法。不同于以往的框架如 Angular 使用的脏检查,在应用改版数据的时候,React 会尽量进行少的比较,然后更具虚拟 DOM 只改变真实 DOM 当中需要被改变的部分。 通过这成单独抽象的逻辑,让 React 有了无限的可能,例如 ReactNative、ReactVR、ReactXP 的实现。

组件是可以讲 UI 切分称为一些独立的、可服用的部分,类似于 js 当中对 function 函数的定义。

以下是组件中的常用类型

  • 函数定义与类定义组件(定义方式不同)
  • 容器与展示组件(功能不同)
  • 有状态与无状态组件(是否有内置属性)
  • 受控与非受控组件(是否直接获取DOM数据)
  • 组合与继承(继承或依赖其他组件)

函数定义与类定义组件

Functional & Class

React 提供了两种定义组件的方法

  1. 通过 JS 的function 来定义组件
  2. 通过 ES6 中的 class 来定义组件

函数定义组件需要定义一个接收 props 作为参数,返回 react 元素的方法即可。

import React from 'reat'
import ReactDOM from 'react-dom'

const Title = (props) => <h1>hello,{props.name}</h1>
ReactDOM.render(<Title name='react' />,
  document.getElementById('app')
);

类定义组件使用ES6中class类的方法来定义

import React from 'reat'
import ReactDOM from 'react-dom'

class Title extends React.Component {
  render() {
    return <h1>hello,{props.name}</h1>
  }
}

ReactDOM.render(<Title name='react' />,
  document.getElementById('app')
);

容器与展示组件

Presentational & Container

  1. 展示组件主要负责组件内容如何展示,数据来自 props 或者与数据无关的 state,大多情况使用函数声明组件。
  2. 容器组件主要关注数据如何交互,数据来自 state 和后端,多数使用 class 类声明组件。

以下例子中,这类组件负担的功能太多了,他只是一个单一的组件,同时需要初始化 state,通过 ajax 获取服务器数据,渲染列表内容。在实际引用中可能还会有更多的功能依赖,在后续无论要修改样式内容、服务器交互都需要修改同一个组件,逻辑严重的耦合。如果功能在同一个组件维护,也不利于团队间的协作。

import React from 'reat'
import ReactDOM from 'react-dom'

class CommentList extends React.Component{
  contructor(props) {
    super(props)
      this.state = {
      comments:[]
    }
  }
    
  componentDidMount() {
    $.ajax({
      url:'my-contents.json',
      dataType:'json',
      sucess:(comments) => {
        this.setState({
          coments:coments
        })
      }
    })
  }
    
  renderComment({body,author}) {
    return <li>{body}-{author}</li>
  }
    
  render() {
    renturn <ul>{this.state.comments.map(this.renderComment)}</ul>
  }   
}

以下例子中, 区分展示组件和容器组件。回复列表如何展示、如何获取的逻辑就被分离到 2 个组件中。第二个展示组件其实也可以拆分为若干小组件。

import React from 'reat'
import ReactDOM from 'react-dom'
import CommentList from './CommentList'

//容器组件
class CommentListContainer extends React.Component {
  contructor(props) {
    super(props)
      this.state = {
      comments:[]
    }
  }
    
  componentDidMount() {
    $.ajax({
      url:'my-contents.json',
      dataType:'json',
      sucess:(comments) => {
        this.setState({
          coments:coments
        })
      }
    })
  }
    
  render() {
    return <CommentList comments={this.state.comments} />
  }
}

CommentList.js

import React from 'reat'

const CommentItem = ({body,author}) => {
  return <li>{body}-{author}</li>
}
const CommentList = (props) => {
  return <ul>{props.comments.map(CommentItem)}</ul>
}

export default CommentList

有状态与无状态组件

Stateful & Stateless

  • 有状态组件可以获取、存储、改变引用本身的数据,伴有state的变化。
  • 无状态组件只接收来自其他组件的传值,只对this.props的调用。
  • 并不是所有的展示组件都是无状态组件,所有的容器组件都是有状态组件。
import React from 'reat'
import ReactDOM from 'react-dom'

//有状态组件
class StatefullLink extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            active:false
        }
    }
    
    handleClick() {
        this.setState({
            active: !this.state.active
        })
    }
    
    render() {
        return <a
            style={{ color: this.state.active ? 'red' : 'black' }}
            onClick={this.handleClick.bind(this)}
        >
            Stateful Link
        </a>
    }
}

// 无状态组件
class StatelessLink extends React.Component {
    contructor(props) {
        super(props)
    }
    
    handleClick() {
        this.props.handleClick(this.props.router)
    }
    
    render() {
        const active = this.props.activeRouter === this.props.router
        return (
            <li>
                <a
                    style={{ color : active ? 'red' : 'black' }}
                    onClick={this.handleClick.bind(this)}
                >
                    Stateless Link
                </a>
            </li>
        )
    }
}

事实上我们编写的组件大部分都是无状态组件

import React from 'reat'
import ReactDOM from 'react-dom'

function SimpleButtonES5(props) {
    return <button>{props.text}</button>
}

const SimpleButtonES6 = props => <button{props.text}</button>

受控与非受控组件

Controlled & Uncontrolled

一般涉及到表单元素时对组件类型的划分方法

  • 一般情况下所有 React 表单元素都是受控组件,每个受控组件都需要对应的事件处理函数
  • React 通过特殊属性 ref 来获取非受控组件

受控组件的值一般由 props 或者 state 传入,用户在元素上交互或者输入内容会引起 state 的变化,在 state 改变之后重新渲染组件,我们才能在页面中看到值的变化。假如没有绑定事件处理函数,用户的输入是不会有效果的,这就是受控的含义所在。

class ControlledInput extends React.Component {
  constructor() {
    super()
    this.state = {
      value:'Please type here...'
    }
  }
    
  handleChange(event){
    console.log('Controlled change:',event.target.value)
    this.setState({
      value:event.target.value
    })
  }
    
  render() {
    return (
      <label>
        Controlled Component:
        <input type="text" value={this.state.value} onChange={(e)=> this.handleChange(e)}/>
      </label>
    )
  }
}

非受控组件类似于传统的DOM表单控件,用户输入不会引起static的改变,我们也不会直接为非受控组件传入值。获取非受控组件的值我们需要使用特殊的ref属性,

class UncontrolledInput extends React.Component {
  constructor() {
    super()
  }
    
  handleChange(event){
    console.log('Controlled change:',event.target.value)
  }
    
  render() {
    return (
      <label>
        Uncontrolled Component:
        <input type="text" defaultValue='Please type here...' ref={(input)=> this.input = input} onChange={(e)=> this.handleChange(e)}/>
      </label>
    )
  }
}

通常情况下,react 当中所有的表单控件都需要是受控组件,我们需要为每一个受控组件编写事件处理函数。比如说注册,你需要写出所有姓名、电话、邮箱的逻辑。当然也有小技巧可以让同一个事件处理函数引用在多个表单组件中,但生产开发中并没有多大的意义,多是在重构的时候使用。还有一些别的库,需要和表单进行交互,这时候使用非受控组件就会方便一些。

组合与继承

Composition & Inheritance

  • 通过组合和 props 传值几乎可以满足所有的场景需求,同样也更符合组件化的理念,就好像相互嵌套的DOM元素一样。
  • 继承的写法虽然生效,但是不符合 React的理念,在 React中,props可以传入任何东西:变量、函数、甚至是组件本身。
import React from 'reat'
import ReactDOM from 'react-dom'

class InheritedButton extends React.Component {
  contructor() {
    super()
    this.state = {
      color: 'red'
    }
  }
    
  render() {
    return (
      <button style={{backgroundColor: this.state.color}} >Inherited Button</button>
    )
  }
}

// 继承
class BlueButton extends InheritedButton {
  contructor() {
    super()
    this.state = {
      color:'blue'
    }
  }
}

// 组合
const CompositedButton = props => <button style={{backgroundColor:props.color}}Composited Button</button>
const YellowButton = () =>  <CompositedButton color="yellow" />

ReactDOM.render(
  <div>
    <BlueButton/>
    <br/>
    <YellowButton>
  </div>,
  document.getElementById('app')
)

如何想实现一些非洁面类型函数的服用,可以单独写在其他的模块当中,再引入组件进行使用。