React组件设计技巧

React组件设计

组件分类

展现组件和容器组件

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

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

// 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 和展现组件 CommentListhtml

// 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>;
  }
}

优点:react

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

有状态组件和无状态组件

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

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 组件:json

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的更新变得同步化。 好比: eventListeners, Ajax, setTimeout 等等。

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底层的Virtual Dom(虚拟DOM树)的diff算法有紧密的关系,因此真正决定同步仍是异步的实际上是Virtual DOMdiff算法。

依赖注入

React中,想作依赖注入(Dependency Injection)其实至关简单。能够经过props来进行传递。可是,若是组件数量不少,而且组件嵌套层次很深的话,这种方式就不太合适。

高阶组件

// inject.jsx
var title = 'React Dependency Injection';
export default function inject(Component) {
  return class Injector extends React.Component {
    render() {
      return (
        <Component
          {...this.state}
          {...this.props}
          title={ title }
        />
      )
    }
  };
}
// Title.jsx
export default function Title(props) {
  return <h1>{ props.title }</h1>;
}
// Header.jsx
import inject from './inject.jsx';
import Title from './Title.jsx';

var EnhancedTitle = inject(Title);
export default function Header() {
  return (
    <header>
      <EnhancedTitle />
    </header>
  );
}

context

React v16.3.0 以前的 Context:

var context = { title: 'React in patterns' };
class App extends React.Component {
  getChildContext() {
    return context;
  }
  // ...
}

App.childContextTypes = {
  title: PropTypes.string
};
class Inject extends React.Component {
  render() {
    var title = this.context.title;
  // ...
  }
}
Inject.contextTypes = {
  title: PropTypes.string
};

以前的 Context 做为一个实验性质的 API,直到 React v16.3.0 版本前都一直不被官方所提倡去使用,其主要缘由就是由于在子组件中使用 Context 会破坏 React 应用的分型架构。

这里的分形架构指的是从理想的 React 应用的根组件树中抽取的任意一部分都还是一个能够直接运行的子组件树。在这个子组件树之上再包一层,就能够将它无缝地移植到任意一个其余的根组件树中。

但若是根组件树中有任意一个组件使用了支持透传的 Context API,那么若是把包含了这个组件的子组件树单独拿出来,由于缺乏了提供 Context 值的根组件树,这时的这个子组件树是没法直接运行的。

而且他有一个致命缺陷:任何一个中间传递的组件shouldComponentUpdate 函数返回false,组件都不会获得更新。

新的Context Api

新的Context Api 采用声明式的写法,而且能够透过shouldComponentUpdate 函数返回false的组件继续向下传播,以保证目标组件必定能够接收到顶层组件 Context 值的更新,一举解决了现有 Context API 的两大弊端,也终于成为了 React 中的第一级(first-class) API

新的 Context API 分为三个组成部分:

  1. React.createContext 用于初始化一个 Context
  2. XXXContext.Provider做为顶层组件接收一个名为 valueprop,能够接收任意须要被放入 Context 中的字符串,数字,甚至是函数。
  3. XXXContext.Consumer做为目标组件能够出如今组件树的任意位置(在 Provider 以后),接收 children prop,这里的 children 必须是一个函数(context =&gt; ())用来接收从顶层传来的 Context
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

事件处理中的this指向问题

class Switcher extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: 'React in patterns' };
  }
  render() {
    return (
      <button onClick={ this._handleButtonClick }>
        click me
      </button>
    );
  }

  _handleButtonClick() {
    console.log(`Button is clicked inside ${ this.state.name }`);
    // 将致使
    // Uncaught TypeError: Cannot read property 'state' of null
  }
}

咱们能够经过下面三种方式简单实现this指向的绑定:

  • constructor 中事先绑定 this._buttonClick = this._handleButtonClick.bind(this);
  • 调用时使用箭头函数 <button onClick={ () => this._buttonClick() }>
  • ES7中的绑定操做符 <button onClick={ ::this._buttonClick() }>

给setState传入回调函数

setState() 不只能接受一个对象,还能接受一个函数做为参数呢,该函数接受该组件前一刻的 state 以及当前的 props 做为参数,计算和返回下一刻的 state。

// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3

this.setState((prevState, props) => ({
  count: prevState.count + props.increment
}));
// Passing object
this.setState({ expanded: !this.state.expanded });

// Passing function
this.setState(prevState => ({ expanded: !prevState.expanded }));

组件切换技巧

import HomePage from './HomePage.jsx';
import AboutPage from './AboutPage.jsx';
import UserPage from './UserPage.jsx';
import FourOhFourPage from './FourOhFourPage.jsx';

const PAGES = {
  home: HomePage,
  about: AboutPage,
  user: UserPage
};

const Page = (props) => {
  const Handler = PAGES[props.page] || FourOhFourPage;

  return <Handler {...props} />
};

React style

组件分类

基础组件, 布局组件, 排版组件

给无状态的纯UI组件应用样式

请保持样式远离那些离不开state的组件. 好比路由, 视图, 容器, 表单, 布局等等不该该有任何的样式或者css class出如今组件上. 相反, 这些复杂的业务组件应该有一些带有基本功能的无状态UI组件组成.

class SampleComponent extends Component {
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <Heading children='Sign In'/>
        <Input
          name='username'
          value={username}
          onChange={this.handleChange}/>
        <Input
          type='password'
          name='password'
          value={password}
          onChange={this.handleChange}/>
        <Button
          type='submit'
          children='Sign In'/>
      </form>
    )
  }
}

// 表达组件(带样式)
const Button = ({
  ...props
  }) => {
  const sx = {
    fontFamily: 'inherit',
    fontSize: 'inherit',
    fontWeight: 'bold',
    textDecoration: 'none',
    display: 'inline-block',
    margin: 0,
    paddingTop: 8,
    paddingBottom: 8,
    paddingLeft: 16,
    paddingRight: 16,
    border: 0,
    color: 'white',
    backgroundColor: 'blue',
    WebkitAppearance: 'none',
    MozAppearance: 'none'
  }

  return (
    <button {...props} style={sx}/>
  )
}

样式模块(style module)

通常来讲, 在组件内写死(hard code)样式应该是要被避免的. 这些有可能被不一样的UI组件分享的样式应该被分开放入对应的模块中.

// 样式模块
export const white = '#fff';
export const black = '#111';
export const blue = '#07c';

export const colors = {
  white,
  black,
  blue
};

export const space = [
  0,
  8,
  16,
  32,
  64
];

const styles = {
  bold: 600,
  space,
  colors
};

export default styles
// button.jsx
import React from 'react'
import { bold, space, colors } from './styles'

const Button = ({
  ...props
  }) => {
  const sx = {
    fontFamily: 'inherit',
    fontSize: 'inherit',
    fontWeight: bold,
    textDecoration: 'none',
    display: 'inline-block',
    margin: 0,
    paddingTop: space[1],
    paddingBottom: space[1],
    paddingLeft: space[2],
    paddingRight: space[2],
    border: 0,
    color: colors.white,
    backgroundColor: colors.blue,
    WebkitAppearance: 'none',
    MozAppearance: 'none'
  };

  return (
    <button {...props} style={sx}/>
  )
};

样式函数(Style Functions)

// Modular powers of two scale
const scale = [
  0,
  8,
  16,
  32,
  64
];

// 经过这个函数去取得一部分的样式
const createScaledPropertyGetter = (scale) => (prop) => (x) => {
  return (typeof x === 'number' && typeof scale[x] === 'number')
    ? {[prop]: scale[x]}
    : null
};
const getScaledProperty = createScaledPropertyGetter(scale);

export const getMargin = getScaledProperty('margin');
export const getPadding = getScaledProperty('padding');
// 样式函数的用法
const Box = ({
  m,
  p,
  ...props
  }) => {
  const sx = {
    ...getMargin(m),
    ...getPadding(p)
  };

  return <div {...props} style={sx}/>
};

// 组件用法.
const Box = () => (
  <div>
    <Box m={2} p={3}>
      A box with 16px margin and 32px padding
    </Box>
  </div>
);

常见小坑

state不更新?

class SampleComponent extends Component {
  // constructor function (or getInitialState)
  constructor(props) {
    super(props);
    this.state = {
      flag: false,
      inputVal: props.inputValue
    };
  }

  render() {
    return <div>{this.state.inputVal && <AnotherComponent/>}</div>
  }
}

这样作的危险在于, 有可能组件的props发生了改变可是组件却没有被更新. 新的props的值不会被React认为是更新的数据由于构造器constructor或者getInitialState方法在组件建立以后不会再次被调用了,所以组件的state再也不会被更新。 要记住, State的初始化只会在组件第一次初始化的时候发生。

class SampleComponent extends Component {
  // constructor function (or getInitialState)
  constructor(props) {
    super(props);
    this.state = {
      flag: false
    };
  }

  render() {
    return <div>{this.props.inputValue && <AnotherComponent/>}</div>
  }
}

更干净的render函数?

更干净的render函数? 这个概念可能会有点让人疑惑.

其实在这里干净是指咱们在shouldComponentUpdate这个生命周期函数里面去作浅比较, 从而避免没必要要的渲染.

class Table extends PureComponent {
  render() {
    return (
      <div>
        {this.props.items.map(i =>
          <Cell data={i} options={this.props.options || []}/>
        )}
      </div>
    );
  }
}

这种写法的问题在于{this.props.options || []} 这种写法会致使全部的Cell都被从新渲染即便只有一个cell发生了改变. 为何会发生这种事呢?

仔细观察你会发现, options这个数组被传到了Cell这个组件上, 通常状况下, 这不会致使什么问题. 由于若是有其余的Cell组件, 组件会在有props发生改变的时候浅对比props而且跳过渲染(由于对于其余Cell组件, props并无发生改变). 可是在这个例子里面, 当optionsnull时, 一个默认的空数组就会被当成Props传到组件里面去. 事实上每次传入的[]都至关于建立了新的Array实例. 在JavaScript里面, 不一样的实例是有不一样的实体的, 因此浅比较在这种状况下老是会返回false, 而后组件就会被从新渲染. 由于两个实体不是同一个实体. 这就彻底破坏了React对于咱们组件渲染的优化.

const defaultval = [];  // <---  也可使用defaultProps
class Table extends PureComponent {
  render() {
    return (
      <div>
        {this.props.items.map(i =>
          <Cell data={i} options={this.props.options || defaultval}/>
        )}
      </div>
    );
  }
}

仍是屡次从新渲染

class App extends PureComponent {
  render() {
    return <MyInput
      onChange={e => this.props.update(e.target.value)}/>;
  }
}
class App extends PureComponent {
  update(e) {
    this.props.update(e.target.value);
  }

  render() {
    return <MyInput onChange={this.update.bind(this)}/>;
  }
}

在上面的两个坏实践中, 每次咱们都会去建立一个新的函数实体. 和第一个例子相似, 新的函数实体会让咱们的浅比较返回false, 致使组件被从新渲染. 因此咱们须要在更早的时候去bind咱们的函数.

class App extends PureComponent {
  constructor(props) {
    super(props);
    this.update = this.update.bind(this);
  }

  update(e) {
    this.props.update(e.target.value);
  }

  render() {
    return <MyInput onChange={this.update}/>;
  }
}

命名

引用命名

React模块名使用帕斯卡命名,实例使用骆驼式命名

// bad
import reservationCard from './ReservationCard';

// good
import ReservationCard from './ReservationCard';

// bad
const ReservationItem = <ReservationCard />;

// good
const reservationItem = <ReservationCard />;

高阶模块命名

// bad
export default function withFoo(WrappedComponent) {
  return function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }
}

// good
export default function withFoo(WrappedComponent) {
  function WithFoo(props) {
    return <WrappedComponent {...props} foo />;
  }

  const wrappedComponentName = WrappedComponent.displayName
    || WrappedComponent.name
    || 'Component';

  WithFoo.displayName = `withFoo(${wrappedComponentName})`;
  return WithFoo;
}

属性命名

避免使用DOM相关的属性来用做其余的用途。

// bad
<MyComponent style="fancy" />

// good
<MyComponent variant="fancy" />

私有函数添加 _ 前缀?

在React模块中,不要给所谓的私有函数添加 _ 前缀,本质上它并非私有的。

为何?_ 下划线前缀在某些语言中一般被用来表示私有变量或者函数。可是不像其余的一些语言,在JS中没有原生支持所谓的私有变量,全部的变量函数都是共有的。尽管你的意图是使它私有化,在以前加上下划线并不会使这些变量私有化,而且全部的属性(包括有下划线前缀及没有前缀的)都应该被视为是共有的。

Ordering React 模块生命周期

class extends React.Component 的生命周期函数:
可选的 static 方法

  • constructor 构造函数
  • getChildContext 获取子元素内容
  • componentWillMount 模块渲染前
  • componentDidMount 模块渲染后
  • componentWillReceiveProps 模块将接受新的数据
  • shouldComponentUpdate 判断模块需不须要从新渲染
  • componentWillUpdate 上面的方法返回 true, 模块将从新渲染
  • componentDidUpdate 模块渲染结束
  • componentWillUnmount 模块将从DOM中清除, 作一些清理任务

点击回调或者事件处理器 如 onClickSubmit()onChangeDescription()

render 里的 getter 方法 如 getSelectReason()getFooterContent()

可选的 render 方法 如 renderNavigation()renderProfilePicture()

render render() 方法

如何定义 propTypes, defaultProps, contextTypes, 等等其余属性...

import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
  id: PropTypes.number.isRequired,
  url: PropTypes.string.isRequired,
  text: PropTypes.string,
};

const defaultProps = {
  text: 'Hello World',
};

class Link extends React.Component {
  static methodsAreOk() {
    return true;
  }

  render() {
    return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>;
  }
}

Link.propTypes = propTypes;
Link.defaultProps = defaultProps;

export default Link;
相关文章
相关标签/搜索