VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 网站开发 > JavaScript >
  • JavaScript教程之React组件设计

组件分类

展示组件和容器组件

展示组件 容器组件
关注事物的展示 关注事物如何工作
可能包含展示和容器组件,并且一般会有DOM标签和css样式 可能包含展示和容器组件,并且不会有DOM标签和css样式
常常允许通过this.props.children传递 提供数据和行为给容器组件或者展示组件
对第三方没有任何依赖,比如store 或者 flux action 调用flux action 并且提供他们的回调给展示组件
不要指定数据如何加载和变化 作为数据源,通常采用较高阶的组件,而不是自己写,比如React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create()
仅通过属性获取数据和回调  
很少有自己的状态,即使有,也是自己的UI状态  
除非他们需要的自己的状态,生命周期,或性能优化才会被写为功能组件  

下面是一个可能会经常写的组件,评论列表组件,数据交互和展示都放到了一个组件里面。

// CommentList.js
class CommentList extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  render() {
    return <ul> {this.state.comments.map(renderComment)} </ul>;
  }
  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
}

我们对上面的组件进行拆分,把他拆分成容器组件 CommentListContainer.js 和展示组件 CommentList

// CommentListContainer.js
class CommentListContainer extends React.Component {
  constructor() {
    super();
    this.state = { comments: [] }
  }
  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }
  render() {
    return <CommentList comments={this.state.comments} />;
  }
}


// CommentList.js
class CommentList extends React.Component {
  constructor(props) {
    super(props);
  }
  render() { 
    return <ul> {this.props.comments.map(renderComment)} </ul>;
  }
  renderComment({body, author}) {
    return <li>{body}—{author}</li>;
  }
}

优势:

  • 展示和容器更好的分离,更好的理解应用程序和UI
  • 重用性高,展示组件可以用于多个不同的state数据源
  • 展示组件就是你的调色板,可以把他们放到单独的页面,在不影响应用程序的情况下,让设计师调整UI
  • 迫使你分离标签,达到更高的可用性

有状态组件和无状态组件

下面是一个最简单的无状态组件的例子:

function HelloComponent(props, /* context */) {
  return <div>Hello {props.name}</div>
}
ReactDOM.render(<HelloComponent name="Sebastian" />, mountNode)

可以看到,原本需要写“类”定义(React.createClass 或者 class YourComponent extends React.Component)来创建自己组件的定义(有状态组件),现在被精简成了只写一个 render 函数。更值得一提的是,由于仅仅是一个无状态函数,React 在渲染的时候也省掉了将“组件类” 实例化的过程。

结合 ES6 的解构赋值,可以让代码更精简。例如下面这个 Input 组件:

function Input({ label, name, value, ...props }, { defaultTheme }) {
  const { theme, autoFocus, ...rootProps } = props
  return (
    <label
      htmlFor={name}
      children={label || defaultLabel}
      {...rootProps}
    >
    <input
      name={name}
      type="text"
      value={value || ''}
      theme={theme || defaultTheme}
      {...props}
    />
  )}
Input.contextTypes = {defaultTheme: React.PropTypes.object};

无状态组件不像上述两种方法在调用时会创建新实例,它创建时始终保持了一个实例,避免了不必要的检查和内存分配,做到了内部优化。

无状态组件不支持 "ref"

高阶组件

高阶组件通过函数和闭包,改变已有组件的行为,本质上就是 Decorator 模式在 React 的一种实现。

当写着写着无状态组件的时候,有一天忽然发现需要状态处理了,那么无需彻底返工:)
往往我们需要状态的时候,这个需求是可以重用的。

高阶组件加无状态组件,则大大增强了整个代码的可测试性和可维护性。同时不断“诱使”我们写出组合性更好的代码。

高阶函数

function welcome() {
    let username = localStorage.getItem('username');
    console.log('welcome ' + username);
}

function goodbey() {
    let username = localStorage.getItem('username');
    console.log('goodbey ' + username);
}

welcome();
goodbey();

我们发现两个函数有一句代码是一样的,这叫冗余唉。(平时可能会有一大段代码的冗余)。

下面我们要写一个中间函数,读取username,他来负责把username传递给两个函数。

function welcome(username) {
    console.log('welcome ' + username);
}

function goodbey(username) {
    console.log('goodbey ' + username);
}

function wrapWithUsername(wrappedFunc) {
    let newFunc = () => {
        let username = localStorage.getItem('username');
        wrappedFunc(username);
    };
    return newFunc;
}

welcome = wrapWithUsername(welcome);
goodbey = wrapWithUsername(goodbey);

welcome();
goodbey();

好了,我们里面的 wrapWithUsername 函数就是一个“高阶函数”。
他做了什么?他帮我们处理了 username,传递给目标函数。我们调用最终的函数 welcome的时候,根本不用关心 username是怎么来的。

举一反三的高阶组件

下面是两个冗余的组件。

import React, {Component} from 'react'

class Welcome extends Component {
    constructor(props) {
        super(props);
        this.state = {
            username: ''
        }
    }

    componentWillMount() {
        let username = localStorage.getItem('username');
        this.setState({
            username: username
        })
    }

    render() {
        return (
            <div>welcome {this.state.username}</div>
        )
    }
}

export default Welcome;
import React, {Component} from 'react'

class Goodbye extends Component {
    constructor(props) {
        super(props);
        this.state = {
            username: ''
        }
    }

    componentWillMount() {
        let username = localStorage.getItem('username');
        this.setState({
            username: username
        })
    }

    render() {
        return (
            <div>goodbye {this.state.username}</div>
        )
    }
}

export default Goodbye;

我们可以通过刚刚高阶函数的思想来创建一个中间组件,也就是我们说的高阶组件。

import React, {Component} from 'react'

export default (WrappedComponent) => {
    class NewComponent extends Component {
        constructor() {
            super();
            this.state = {
                username: ''
            }
        }

        componentWillMount() {
            let username = localStorage.getItem('username');
            this.setState({
                username: username
            })
        }

        render() {
            return <WrappedComponent username={this.state.username}/>
        }
    }

    return NewComponent
}
import React, {Component} from 'react';
import wrapWithUsername from 'wrapWithUsername';

class Welcome extends Component {

    render() {
        return (
            <div>welcome {this.props.username}</div>
        )
    }
}

Welcome = wrapWithUsername(Welcome);

export default Welcome;
import React, {Component} from 'react';
import wrapWithUsername from 'wrapWithUsername';

class Goodbye extends Component {

    render() {
        return (
            <div>goodbye {this.props.username}</div>
        )
    }
}

Goodbye = wrapWithUsername(Goodbye);

export default Goodbye;

看到没有,高阶组件就是把 username 通过 props 传递给目标组件了。目标组件只管从 props里面拿来用就好了。

为了代码的复用性,我们应该尽量减少代码的冗余。

  1. 提取共享的state,如果有两个组件都需要加载同样的数据,那么他们会有相同的 componentDidMount 函数。
  2. 找出重复的代码,每个组件中constructor 和 componentDidMount都干着同样的事情,另外,在数据拉取时都会显示Loading... 文案,那么我们应该思考如何使用高阶组件来提取这些方法。
  3. 迁移重复的代码到高阶组件
  4. 包裹组件,并且使用props替换state
  5. 尽可能地简化

组件开发基本思想

单功能原则

使用react时,组件或容器的代码在根本上必须只负责一块UI功能。

让组件保持简单

  • 如果组件根本不需要状态,那么就使用函数定义的无状态组件。

  • 从性能上来说,函数定义的无状态组件 > ES6 class 定义的组件 > 通过 React.createClass() 定义的组件。

  • 仅传递组件所需要的属性。只有当属性列表太长时,才使用{...this.props}进行传递。

  • 如果组件里面有太多的判断逻辑(if-else语句)通常意味着这个组件需要被拆分成更细的组件或模块。

  • 使用明确的命名能够让开发者明白它的功能,有助于组件复用。

基本准则

  • shouldComponentUpdate中避免不必要的检查.

  • 尽量使用不可变数据类型(Immutable).

  • 编写针对产品环境的打包配置(Production Build).

  • 通过Chrome Timeline来记录组件所耗费的资源.

  • componentWillMount或者componentDidMount里面通过setTimeOut或者requestAnimationFram来延迟执行那些需要大量计算的任务.

组件开发技巧

form表单里的受控组件和不受控组件

受控组件

在大多数情况下,我们推荐使用受控组件来实现表单。在受控组件中,表单数据由 React 组件负责处理。下面是一个典型的受控组建。

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

设置表单元素的value属性之后,其显示值将由this.state.value决定,以满足React状态的同一数据理念。每次键盘敲击之后会执行handleChange方法以更新React状态,显示值也将随着用户的输入改变。

对于受控组件来说,每一次 state(状态)变化都会伴有相关联的处理函数。这使得可以直接修改或验证用户的输入和提交表单。

不受控组件

因为不受控组件的数据来源是 DOM 元素,当使用不受控组件时很容易实现 React 代码与非 React 代码的集成。如果你希望的是快速开发、不要求代码质量,不受控组件可以一定程度上减少代码量。否则。你应该使用受控组件。

一般情况下不受控组件我们使用ref来获取DOM元素进行操作。

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={(input) => this.input = input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

组件条件判断

三元函数组件判断渲染

const sampleComponent = () => {
  return isTrue ? <p>True!</p> : <p>false!</p>
};

使用&&表达式替换不必要的三元函数

const sampleComponent = () => {
  return isTrue ? <p>True!</p> : <none/>
};
const sampleComponent = () => {
  return isTrue && <p>True!</p>
};

需要注意的是如果isTrue 为 0 ,其实会转换成 false,但是在页面中显示的时候,&&还是会返回0显示到页面中。

多重嵌套判断

// 问题代码
const sampleComponent = () => {
  return (
    <div>
      {flag && flag2 && !flag3
        ? flag4
        ? <p>Blah</p>
        : flag5
        ? <p>Meh</p>
        : <p>Herp</p>
        : <p>Derp</p>
      }
    </div>
  )
};

解决方案:

  • 最佳方案: 将逻辑移到子组件内部
  • 使用IIFE(Immediately-Invoked Function Expression 立即执行函数)
  • 满足条件的时候使用return强制跳出函数
const sampleComponent = () => {
  const basicCondition = flag && flag2 && !flag3;
  if (!basicCondition) return <p>Derp</p>;
  if (flag4) return <p>Blah</p>;
  if (flag5) return <p>Meh</p>;
  return <p>Herp</p>
}

setState异步性

在某些情况下,React框架出于性能优化考虑,可能会将多次state更新合并成一次更新。正因为如此,setState实际上是一个异步的函数。 如果在调用setState()函数之后尝试去访问this.state,你得到的可能还是setState()函数执行之前的结果。

但是,有一些行为也会阻止React框架本身对于多次state更新的合并,从而让state的更新变得同步化。 比如: eventListenersAjaxsetTimeout 等等。

React框架之所以在选择在调用setState函数之后立即更新state而不是采用框架默认的方式,即合并多次state更新为一次更新,是因为这些函数调用(fetch,setTimeout等浏览器层面的API调用)并不处于React框架的上下文中,React没有办法对其进行控制。React在此时采用的策略就是及时更新,确保在这些函数执行之后的其他代码能拿到正确的数据(即更新过的state)。

解决setState函数异步的办法?

根据React官方文档,setState函数实际上接收两个参数,其中第二个参数类型是一个函数,作为setState函数执行后的回调。通过传入回调函数的方式,React可以保证传入的回调函数一定是在setState成功更新this.state之后再执行。

this.setState({count: 1}, () => {
    console.log(this.state.count); // 1
})

React源码中setState的实现

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
    typeof partialState === 'function' ||
    partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
    'function which returns an object of state variables.'
  );
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

updater的这两个方法,和React