react技术实践

react介绍

React 项目是Facebook的内部项目,因为该公司对市场上所有JavaScript框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

由于 React的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。

这个项目本身也越滚越大,从最早的UI引擎变成了一整套前后端通吃的 Web App 解决方案。衍生的 React Native 项目,目标更是宏伟,希望用写WebApp的方式去写NativeApp。如果能够实现,整个互联网行业都会被颠覆,因为同一组人只需要写一次 UI ,就能同时运行在服务器、浏览器和手机。React主要用于构建UI。

你可以在React里传递多种类型的参数,如声明代码,帮助你渲染出UI、也可以是静态的HTML DOM元素、也可以传递动态变量、甚至是可交互的应用组件。

特点:

  1. 声明式设计:React采用声明范式,可以轻松描述应用。
  2. 高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。
  3. 灵活:React可以与已知的库或框架很好地配合。

react 尤物志技术实践

尤物志是微博旗下的电商版块,第一版的尤物志开发工作我并没有亲自参与,主要参与第二次的改版工作以及重构工作。下面介绍一下尤物志的技术栈。用到的主要技术有NodeJS+React+Webpack+ES6+Babel+SASS,其中核心是react技术,首先从react组件说起,任何一个复杂的应用,都是由一个简单的应用发展而来的,当应用还很简单的时候,因为功能少,可能只需要一个组件就够了,但是,随着功能的增加,把越来越多的功能放在一个组件中就会显得臃肿和难以管理。就和一个人专注做一件事情一样,也应该尽量保持一组件只做一件事情,当开发者发现一个组件功能太多,代码量太大的时候,就要考虑拆分这个组件, 用多个小的组件来代替,每个小的组件只关注实现单个功能,但是这些功能组合起来,也能够满足复杂的实际业务需求。这就是“分而治之”的策略,把问题你分解为多个小的问题,这样既绕你故意解决也方便维护。虽然这是一个好策略,但是也不能滥用,只有必要的时候采取拆分组件,不然可能得不偿失。

根据软件设计的通则,组件的划分为应当为高内聚,低耦合的原则。

高内聚指的是把逻辑紧密相关的内容放在一个组件当中,用户界面无外乎内容,交互行为和样式。传统上,内容上由HTML表示,交互行为放在JavaScript代码文件中,样式放在css文件当中进行定义。虽然是一个功能,但是却要放在三个文件当中,不符合高内聚的原则。react却不是这样,在react当中,这些东西都可以放在一个文件当中,因此,react天生具有高内聚的特点。

具体到尤物志项目当中,是根据不同的功能来划分组件的,对于仅仅是样式或者内容上有区别的功能模块,尽量复用前面已经开发好的组件,而不是再去重新去开发,做到一个模块不管放到什么地方都可以正常使用,对于后期维护都具有重要的意义。

尤物志项目中涉及到最多的就是父子组件之间的通信,而父子组件的之间的通信主要是父组件向子组件传递信息,父组件向子组件传递信息,主要使用props进行传递,子组件通过this.props.属性名来获取之前在父组件中已经定义好的信息,如果是层次较多,比如父元素向子元素的子元素(孙子元素)传递信息,这个时候比较笨的方法就是利用props一层一层往下传递,但是这样的如果嵌套不是太深的话还可以使用,如果嵌套层级太深的话,就不再适用,这个时候我们就要使用context来进行数据的传递,下面看一个案列:

<span style="font-size:18px;">var Button = React.createClass({
  // 必须指定context的数据类型
  contextTypes: {
    color: React.PropTypes.string
  },
  render: function() {
    return (
      <button style=>
        {this.props.children}
      </button>
    );
  }
});

var Message = React.createClass({
  render: function() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
});

var MessageList = React.createClass({
  //父组件要定义 childContextTypes 和 getChildContext()
  childContextTypes: {
    color: React.PropTypes.string
  },
  getChildContext: function() {
    return {color: "purple"};
  },
  render: function() {
    var children = this.props.messages.map(function(message) {
      return <Message text={message.text} />;
    });
    return <div>{children}</div>;
  }
});</span>

以上代码中通过添加 childContextTypes 和 getChildContext() 到 第一层组件MessageList ( context 的提供者),React 自动向下传递数据然后在组件中的任意组件(也就是说任意子组件,在此示例代码中也就是 Button )都能通过定义 contextTypes(必须指定context的数据类型) 访问 context 中的数据。这样就不需要通过第二层组件进行传递了。

指定数据并要将数据传递下去的父组件要定义 childContextTypes 和 getChildContext() ;想要接收到数据的子组件 必须定义 contextTypes 来使用传递过来的 context 。

使用这种方法减少了之前那种多余的嵌套,大大优化了代码,有利于后期的维护。

虽然项目中并没有涉及到子组件向父组件传递信息,但是这里还是要简单的讲一下,案列如下:


<span style="font-size:18px;">// 父组件
var MyContainer = React.createClass({
  getInitialState: function () {
    return {
      checked: false
    };
  },
  onChildChanged: function (newState) {
    this.setState({
      checked: newState
    });
  },
  render: function() {
    var isChecked = this.state.checked ? 'yes' : 'no';
    return (
      <div>
        <div>Are you checked: {isChecked}</div>
        <ToggleButton text="Toggle me"
          initialChecked={this.state.checked}
          callbackParent={this.onChildChanged}
          />
      </div>
    );
  }
});

// 子组件
var ToggleButton = React.createClass({
  getInitialState: function () {
    return {
      checked: this.props.initialChecked
    };
  },
  onTextChange: function () {
    var newState = !this.state.checked;
    this.setState({
      checked: newState
    });

    //这里将子组件的信息传递给了父组件
    this.props.callbackParent(newState);
  },
  render: function () {
    // 从(父组件)获取的值
    var text = this.props.text;
    // 组件自身的状态数据
    var checked = this.state.checked;
        //onchange 事件用于单选框与复选框改变后触发的事件。
    return (
        <label>{text}: <input type="checkbox" checked={checked}                 onChange={this.onTextChange} /></label>
    );
  }
});</span>

以上例子中,在父组件绑定callbackParent={this.onChildChanged},在子组件利用this.props.callbackParent(newState),触发了父级的的this.onChildChanged方法,进而将子组件的数据(newState)传递到了父组件。 这样做其实是依赖 props 来传递事件的引用,并通过回调的方式来实现的

那么还有一种传递是没有任何嵌套关系的组件之间传值(比如:兄弟组件之间传值)

这个例子需要引入一个PubSubJS 库,通过这个库你可以订阅的信息,发布消息以及消息退订。 具体可参考下面的内容 PubSubJS

<span style="font-size:18px;">// 定义一个容器(将ProductSelection和Product组件放在一个容器中)
var ProductList = React.createClass({
    render: function () {
      return (
        <div>
          <ProductSelection />
          <Product name="product 1" />
          <Product name="product 2" />
          <Product name="product 3" />
        </div>
      );
    }
});
// 用于展示点击的产品信息容器
var ProductSelection = React.createClass({
  getInitialState: function() {
    return {
      selection: 'none'
    };
  },
  componentDidMount: function () {
    //通过PubSub库订阅一个信息
    this.pubsub_token = PubSub.subscribe('products', function (topic, product) {
      this.setState({
        selection: product
      });
    }.bind(this));
  },
  componentWillUnmount: function () {
    //当组件将要卸载的时候,退订信息
    PubSub.unsubscribe(this.pubsub_token);
  },
  render: function () {
    return (
      <p>You have selected the product : {this.state.selection}</p>
    );
  }
});

var Product = React.createClass({
  onclick: function () {
    //通过PubSub库发布信息
    PubSub.publish('products', this.props.name);
  },
  render: function() {
    return <div onClick={this.onclick}>{this.props.name}</div>;
  }
});</span>

ProductSelection和Product本身是没有嵌套关系的,而是兄弟层级的关系。但通过在ProductSelection组件中订阅一个消息,在Product组件中又发布了这个消息,使得两个组件又产生了联系,进行传递的信息。所以根据我个人的理解,当两个组件没有嵌套关系的时候,也要通过全局的一些事件等,让他们联系到一起,进而达到传递信息的目的。

react 声明周期介绍

实例化 首次实例化

getDefaultProps getInitialState componentWillMount render componentDidMount

实例化完成后的更新

getInitialState componentWillMount render componentDidMount

存在期 组件已存在时的状态改变

componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate 销毁&清理期 componentWillUnmount 说明 生命周期共提供了10个不同的API。

现在我们通常用ES6的方法创建组件,这个时候并不会发生1和2这2个过程。

1.getDefaultProps 作用于组件类,只调用一次,返回对象用于设置默认的props,对于引用值,会在实例中共享。

2.getInitialState 作用于组件的实例,在实例创建时调用一次,用于初始化每个实例的state,此时可以访问this.props。

3.componentWillMount 在完成首次渲染之前调用,此时仍可以修改组件的state。它既可以在浏览器端被调用,也可以在服务器端被调用。

4.render 必选的方法,创建虚拟DOM,该方法具有特殊的规则:

只能通过this.props和this.state访问数据 可以返回null、false或任何React组件 只能出现一个顶级组件(不能返回数组) 不能改变组件的状态 不能修改DOM的输出

5.componentDidMount 真实的DOM被渲染出来后调用,在该方法中可通过this.getDOMNode()访问到真实的DOM元素。此时已可以使用其他类库来操作这个DOM。

在服务端中,该方法不会被调用这是它和componentWillMount的区别。

6.componentWillReceiveProps 很多说法是组件接收到新的props时调用,其实这是不正确的,实际上是只要父组件的render函数被调用,在render函数里面被渲染的子组件就会经历更新过程,不管父组件传给子组件的props是否发生改变,都会触发此函数,下面是接收到新的props的时候,并将其作为参数nextProps使用,此时可以更改组件props及state。

  componentWillReceiveProps: function(nextProps) {
        if (nextProps.bool) {
            this.setState({
                bool: true
            });
        }
    }

7.shouldComponentUpdate 组件是否应当渲染新的props或state,返回false表示跳过后续的生命周期方法,通常不需要使用以避免出现bug。在出现应用的瓶颈时,可通过该方法进行适当的优化。

在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用

8.componentWillUpdate 接收到新的props或者state后,进行渲染之前调用,此时不允许更新props或state。

9.componentDidUpdate 完成渲染新的props或者state后调用,此时可以访问到新的DOM元素。

10.componentWillUnmount 组件被移除之前被调用,可以用于做一些清理工作,在componentDidMount方法中添加的所有任务都需要在该方法中撤销,比如创建的定时器或添加的事件监听器。

赞 赏