不多教程会教你的:ReactJS最佳实践

1 前言

我本身使用ReactJS有必定时间了,慢慢的也积累了不少所谓的Best Practice,以前内容比较少,都是记在脑子里,后来就记在有道云笔记里,并且本来打算在团队内部开分享会议的时候,在团队内部分享的,但因为种种缘由一直没有机会,索性干脆整理一下分享在社区了,也省的像以前用AngularJS的时候,有不少的心得在长时间不用以后慢慢都忘记了。正所谓好记性不如烂笔头嘛,尤为是时间久了。html

如下全部的代码,内容几乎都是ES6,若是你尚未开始用ES6代码,能够在准备好简历以后,恶狠狠的指着大家总监的鼻子羞辱他,由于我我的以为离开ES6,ReactJS带来的开发的便捷性会大打折扣。好不了解ES6的猿能够去网上找阮一峰老师的在线教程,九浅一深,啊不!由浅入深的学习ES6。文章也假设你对React有必定的了解,知道React的各个生命周期阶段的函数,但不要求精通。(这不废话嘛!精通了还看这篇文章干吗。)react

2 奇淫技巧

2.1 context的妙用

2.1.1 使用场景

话很少说,看图:
图片描述express

图片来源:https://segmentfault.com/a/11...segmentfault

有时候会有这样一种状况,组件嵌套层次很深,而最又内层组件又须要最外层组件的一个属性,好比图中D组件须要A组件中的一个属性username,这时候最容易想到的就是:A组件内把这个username做为props传给B组件,B组件再原封不动的传给C组件,C再传给D,而B,C组件有可能都没有用到这个username,只是做为中间媒介在传递属性。可是你依然不得不书写这个props,针对这种状况就能够用到ReactJS中的context。数组

2.1.2 使用方法

// 组件A
class A extends Component {
    // 其余代码省略,只上关键代码
    constructor(props) {
        super(props);
        this.state = {
            username: 'Neil'
        };
    }
    getChildContext() {
        return {
            username: this.state.username
        };
    }
}

A.childContextTypes = {
    username: PropTypes.string
};

// 组件D
class D extends Component {
    render() {
        return <div>{this.context.username}</div>
    }
}
D.contextChild = {
    username: PropTypes.string
}

能够看到context的使用仍是比较简单的,根组件A做为Context Provider,须要在其中添加一个成员方法getChildContext(该方法必须返回一个对象),和一个静态属性childContextTypes。而后为任何一个你想使用该context的子组件添加一个静态属性contextTypes来告诉React你要用哪一个contextless

2.1.3 注意事项

虽然使用比较简单,可是官方也明确表示,并不推荐使用context,由于这会使得本来简单并可控的React单向数据流变得复杂,不可预期。因此使用中仍是有如下几点须要注意:ide

  • 推荐把不常常变更的值做为context传递,好比在组件树中,username就像一个全局常量同样,不常常变更。
  • 在上面的例子中,组件使用context之后,在D组件的生命周期的这几个函数中会有额外多余的参数:函数

    • constructor(props, context)
    • componentWillReceiveProps(nextProps, nextContext)
    • shouldComponentUpdate(nextProps, nextState, nextContext)
    • componentWillUpdate(nextProps, nextState, nextContext)

在React15以前的本版本中,componentDidUpdate会有prevContext参数,可是最新的React16版本再也不接收此参数。性能

  • 在无状态的函数式组件(Stateless Functional Components,关于什么是函数式组件,后面会讲到)中也可使用context,好比:
const D = (props, context) => {
    return <div>{context.username}</div>
};

D.contextChild = {username: PropTypes.string};
  • 尽可能不要更新contextcontext只适用于传递那些不常常变更的变量,可是若是你不得不更改context,例如上面的例子中A组件中的username实际上来自于A的state,假如咱们在A中调用this.setState({username: 'Michel'}), 那么在D组件中context也会更新。由于A组件中声明的getChildContext方法在A组件的初始化阶段和更新阶段都会被调用。但要注意的是,假如B组件或者C组件实现了shouldComponentUpdate,并在某些条件下返回了false,那么,D组件中的context将不会再更新。由于此时,B组件或者C组件的render并不会从新被调用。D组件也就不会更新(换句话说,在D组件生命周期的更新阶段,任何的hook函数都不会被调用)。因此咱们应极力避免更新context,由于这会使应用的数据流变得很是混乱,这背离了React的设计思想。

2.2 自定义组件ref属性的使用

React组件基本分为两类,一类是原生的组件好比div,span,ul等等这类的HTML标签(其实你也能够直接叫他HTML DOM元素,只是我本身认为他也是一类组件),一类是自定义组件,包括用class定义的组件和用函数定义的组件,ref属性在这两种组件中所表明的含义是不同的。学习

2.2.1 refHTML标签上使用

看代码:

class CustomComponent extends React.Component {
  render() {
    return (
      <div>
      <input type="text"
          ref={(input) => { this.textInput = input; }} />
      </div>
    );
  }
}

此时在CustomComponent组件内部的input标签上有一个ref属性,该属性是一个匿名函数,该函数有一个形参,表明的是input这个标签的DOM对象的引用,而后再函数体中将该对象赋值给this.textInput属性。

2.2.2 ref在自定义组件上的应用

class Parent extends React.Component {
  render() {
    return (<Child ref={component => {this.child = component}}/>);
  }
}

class Child extends React.Component {
  render() {return (<div> I am a child.</div>);}
}

上例中,咱们在父组件Parent中调用了Child组件,Child组件上一样传递了这个特殊的属性ref,在匿名函数体内,一样将函数的参数赋值给this.child,此时在控制台中打印出this.child
图片描述

能够看到该对象就是组件实例化以后的对象,包含props, contextstate等属性。这种特性能够容许咱们访问某个子组件的内部状态

2.2.3 ref在函数式组件上的使用

ref属性不能够在函数式组件上使用,假如将上例的Child改成:

const Child = (props) => {
    return (<div> I am a child.</div>);
}

此时在Parent组件中便不能为Child组件添加ref属性,由于函数式组件没有实例,可是能够在函数式组件内部使用ref属性,好比咱们在Child组件中使用ref

2.3 将某个props复制给state

之因此想把这个拿出来讲一说是由于在我刚开始学React的时候,团队的加拿大Leader在review我代码的时候,看到我写了

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

以后,要求我不要这么写,说这么写是不规范的。其实后来看官方文档,这么写也是能够的。只不过在组件的props.username更新以后,this.state.username并再也不更新。若是想保证二者值同步,能够在组件的生命周期函数componentWillReceiveProps中更新state,由于在组件的更新阶段,componentWillReceiveProps函数是惟一一个能够在更新组件以前再次调用setState来更新组件状态的地方,须要注意的是若是的组件的更新是由于某个地方调用setState,那么在组件的更新阶段这个函数并不会被调用,以免陷入死循环。

componentWillReceiveProps(nextProps) {
    this.setState({username: nextProps.username});
}

3 最佳实践

3.1 避免直接在组件上写用bind函数

不少React教程在讲到React事件处理的时候,都会有相似的写法:

class ButtonList extends Component {
    constructor(props) {
        super(props);
        this.state = {
            state: props.username
        };
    }
    
    handleClick() {
        const {count} = this.state;
        this.setState({count: count + 1});
    }
    
    render() {
        return (
          // 假设Button是咱们自定义的一个组件,这个组件须要一个onClick属性,属性类型必须为函数
          <Button onClick={this.handleClick.bind(this)} />
        )    
    }
}

这样的写法并无错,可是可有可能会引发一些性能问题,由于ButtonListrender函数每一次被调用的时候,都会建立一个新的函数,而后赋值给onClick属性,在Button组件内部属性的比较又是用===,在某些状况下,这样会引发Button组件没必要要的从新渲染。深究其缘由是由于每次调用bind函数都会返回一个新的函数(还没完全弄明白bind函数原理的猿能够看这篇文章)。好比,下面的代码:

function print() {
    console.log(this.a);
}
let obj = {a: 'A'}
print.bind(obj) === print.bind(obj);

最后一行代码比较的结果始终是false,由于每次调用bind函数都会返回一个新的函数对象的引用。这就是为何要尽可能避免在行内写bind函数,<Button type='button' onClick={(event) => {}} />这样的写法也有相同的问题,由于这样至关于每次都声明一个新的匿名函数。最佳的写法是在constructor内写this.handleClick = this.handleClick.bind(this)(至关于在组件初始化时,就把bind函数返回的新函数保存在一个成员变量中,这样,在组件的更新阶段,每次给onClick属性都传递的都是同一个函数的引用),并在组件上这样写<Button onClick={this.handleClick} />
可是,假如在演示组件ButtonList有十几个函数须要绑定,你可能会抱怨,每添加一个事件处理函数,都要写相似这样的代码,致使组件臃肿不堪。身为一个有逼格的程序猿,怎么能够干这种重复的机械运动呢!这时,你能够这样写:

constructor() {
    // 省略其余非关键代码,并假设咱们已经声明了如下事件处理函数
    this.bind([
      'eventHandler1',
      'eventHandler2',
      'eventHandler3',
      'eventHandler4',
      'eventHandler5'
    ])
}

bind(methodArray) {
    methodArray.forEach(method => this[method] = this[method].bind(this));
}

3.2 多用函数式组件(Stateless Functional Component, SFC)

3.2.1 使用方法

React中自定义组件,最简单的写法能够是这样:

function Custom(props) {
    // props以函数参数的形式传递
    return(<div>This is a functional component, {props.name}</div>);
}

也能够用ES6语法这样写:

class Custom extend Component {
    render() {
        return(<div>This is a functional component</div>);
    }
}

这两种写法属于不一样类型的组件,前一种称为函数式组件,后一种称为class类组件。这两种组件在使用上有一些区别,好比2.2.2小结讲到的ref属性的使用。这里推荐使用第一种写法。由于第一种写法的代码量更少,并且函数式组件也有更加优秀的性能表现(关于两种组件的性能比较,我还没找更多的证据,听说可以减小不少无心义的检测和内存分配)总之这样写,更加优雅(装逼)啦。若是你在用ES6语法,前一种写法还能够是这样:

const Custom = ({name}) => {
    return(<div>This is a functional component, {name}</div>);
};

这中写法最迷人的地方在于代码量不多,代码易读,看函数声明很容知道该组件须要哪些属性。

3.2.2 注意事项

函数式组件虽好,但不是任何状况下都能使用函数式组件。这里给两条参照标准:

  1. 正如该组件的英文名称:Stateless Functional Component,只有在组件没有状态的时候才适用。
  2. 函数式组件没有生命周期的hook函数,若是你想调用任何一个生命周期钩子函数,请使用类组件。

3.3 避免直接修改porps或者state

关于这条,不少人在一开始学习React的时候就被灌输了这个概念:不能在组件内部修改porps ,修改state要用setState。可是在一些复杂的状况下,你们每每会在不经意间犯了这两个错误。看下面的代码:

// 假设data是这样的结构:
// data = [
//     {
//         name: 'Lily',
//         age: 19
//     },
//     {
//         name: 'Tom',
//         age: 20
//     }
// ];
const data = this.props.data;
data.push({name: 'Neil', age: 22});

包括个人同事在内,他们常常会写相似这样的代码,而后困惑的跑来问我,为何组件没有按照预期更新,由于这样的写法,已经更新了props。这涉及到JS中一个很重的知识点:JS中将对象(Function和Array也是对象)赋值给某一个变量,本质上是把对象的引用赋值给这个变量,做为函数的实参传递时也是同样的道理(若是还不明白什么是对象的引用,网上有不少教程解释的很清楚,我比较懒,就不解释这么基础的概念了)。好比下面的代码:

let obj = {a: 1};
function test(obj) {
    obj.a = 2;
}
test(obj);
console.log(obj.a); // 打印出2

因此在组件内这样的代码:data.push({name: 'Neil', age: 22})实际上已经更改了实际上已经不经意间在子组件内修改了父组件的传来的props。若是将示例中的代码换成const data = this.state.data; data.push({name: 'Neil', age: 22});后也会有相同的问题。正确的写法应该是:

// 利用ES6的spread语法
// Spread会返回一个新数组,并将新数组的引用赋值给变量data,
// 修改data也就不会影响this.props.data
const data = [...this.props.data];

// 或者不利用ES6你在ES5中也能够这样:
// concat函数也会返回一个新的数组
var data = [].concat(this.props.data);

// 若是this.props.data是一个对象(字面量对象,非数组对象或函数对象)
// assign函数也会返回一个新的对象。
var data = Object.assign({}, this.props.data);


data.push({name: 'Neil', age: 22});

4 最后

还有其余的东西,因为篇幅和时间有限没来得及写,之后有机会写一写。文章有任何错误之处,请不吝赐教或轻喷。附上参考文章及连接:

https://reactjs.org/
https://segmentfault.com/a/11...
http://www.react.express/
https://segmentfault.com/a/11...
http://www.admin10000.com/doc...

相关文章
相关标签/搜索