Published on

React从零学起

Authors
  • avatar
    Name
    Pursue
    Twitter

初接触 React,除了不习惯其组件化的设计原则外,往往它所‘依赖’的众多 module 也会让初学者感到困惑,使得不知从何学起。此文只是我对 React 的一些浅析,希望能帮助想要学习 React 的朋友入门。

1.React 从来就是独立的

正如上面我提到的,React'依赖'了很多 module,但是这种依赖并不是所谓的耦合依赖,只是为了更好的去实现 React。换句话说,React 只是一个 View 层面的框架,它可以和其他 module 自然的融合(更好的去实现)。

我们可以只利用 React 去实现官网上那个 Counter 的 demo,这里我做了一个简易版。

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Redux counter example</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/bundle.js"></script>
  </body>
</html>

页面只引了一个 js 文件,该文件为 webpack 打包而成,具体 webpack 打包原理不在这里赘述。

var React = require('react'); var Counter = React.createClass({
getInitialState: function() { return {value: 0}; }, plus: function() { this.setState({ value:
++this.state.value }); }, minus: function() { this.setState({ value: --this.state.value }); },
render: function() { return (
<div>
  <button onClick="{this.plus}">+</button>
  <span>{this.state.value}</span>
  <button onClick="{this.minus}">-</button>
</div>
); } }); module.exports = Counter;

这是典型的 React 的 Component,它的内部实现了计数的算法以及 state 的管理机制,这个 Component 的实例就是计数器,该计数器也很简单,可以实现增加和减少。

最后是 index.js

var React = require('react')
var ReactDOM = require('react-dom')
var MyCounter = require('./components/MyCounter')

ReactDOM.render(<MyCounter />, document.getElementById('root'))

到此,我们就利用 React 实现了一个小小的计数器,尽管它很简单,但是却未使用任何其他 module,所以我在上面提到,React 本身就是独立的。

所以,引用其他 module,只是为了实现更复杂的 React App,使得其更具有扩展性。

2.Container

那么问题来了,如果我还像要一个类似的 Component,但是每次计数的时候不是加 1 或者减 1,而是乘 2 或除 2,那怎么做呢?

你可别告诉我重新一个类似上面 MyCounter 的 Component,然后绑定不同的事件,那如果是这样的话 React 也太 low 了吧,这还叫什么组件化呢,组件化最基本的特点就是复用啊。

所以 React 期望我们这么做:

对于任何 Compoent,尽量将其作为静态展示的 Component,即其只负责展示 UI,然后在它的外层嵌套一个 Container, Container 中定义了该 Compoent 所需要的参数以及方法,这样,当我们需要复用 Component 时,UI 已经是现成的了,而 Container 中的逻辑部分也可以共享,换个“壳子”就是一个具有其他功能的 Compoent 了。

于是,分解如下:

MyCounterContainer.js

var React = require('react')
var Counter = require('../components/MyCounter')

var CounterContainer = React.createClass({
  getInitialState: function () {
    return {
      value: this.props.value,
    }
  },
  plus: function () {
    this.setState({
      value: this.state.value + 1,
    })
  },
  minus: function () {
    this.setState({
      value: this.state.value - 1,
    })
  },
  render: function () {
    return <Counter plus={this.plus} minus={this.minus} value={this.state.value} />
  },
})

module.exports = CounterContainer

MyCounter

var React = require('react')

var Counter = React.createClass({
  render: function () {
    return (
      <div>
        <span>{this.props.value}</span>
        <button onClick={this.props.plus}>+</button>
        <button onClick={this.props.minus}>-</button>
      </div>
    )
  },
})

module.exports = Counter

index.js

var React = require('react')
var ReactDOM = require('react-dom')
var MyCounterContainer = require('./container/MyCounterContainer')

ReactDOM.render(<MyCounterContainer value={0} />, document.getElementById('root'))

UI 与逻辑分离成功,是不是感觉瞬间清爽许多。 关于什么时 Contianer Component 和 Presentation Component,推荐此文

3.Use store to help dispatch actions

分离了 UI 后,的确逻辑上清楚了许多,但仔细观察会发现,上面的 MyCounterContainer 状态的改变只是两个 button。而 React 认为 Component 的状态变化必定是由一个行为,即 action 造成的,因此,我们需要将上面的加减法抽象为一个行为驱动的事件,即一个行为对应一种状态结果。而 redux 就是干这事儿的,它通过 createStore 去创建一个 store,这个 store 可以管理和知晓这个 Component 的状态,它通过 dispatch 分发 action 然后得到最新的状态结果。

利用 store,我们将 MyCounterContainer 重构如下:

var React = require('react')
var Counter = require('../components/MyCounter')
var createStore = require('redux').createStore

var counter = function (state, action) {
  switch (action.type) {
    case 'PLUS':
      return state + action.value
    case 'MINUS':
      return state - action.value
    default:
      return state
  }
}

var store = createStore(counter, 1000)
var CounterContainer = React.createClass({
  plus: function () {
    var nextState = store.dispatch({
      type: 'PLUS',
      value: 2,
    })
    this.setState(nextState)
  },
  minus: function () {
    var nextState = store.dispatch({
      type: 'MINUS',
      value: 2,
    })
    this.setState(nextState)
  },
  render: function () {
    return <Counter plus={this.plus} minus={this.minus} value={store.getState()} />
  },
})

module.exports = CounterContainer

4.Use connect to manage the dispatch and reducer

仔细观察上次重构,不难发现还是有些问题:

其一,尽管利用 store 帮我们管理了 state,但是还是得我们手动 setState,太过耦合。

其二,对于传递给子 Component 的参数,还是写死在 Container 里,不具有封装性和灵活性。

为了解决这个问题,我们可以利用 react-redux 的 connect 来解决。 connect 可以把自定义的 state 和 dispatch 分发事件绑定到 Component 上,其中 mapStateToProps 正如其名,可以将 state 作为 Component 的 props 传递下去;而 mapDispatchToProps 则可以把 action 触发逻辑传递下去,这样我们可以很灵活的传递功能事件了。

利用 connect 我们继续重构,MyCounterContainer 如下:

var React = require('react')
var Counter = require('../components/MyCounter')
var createStore = require('redux').createStore
var connect = require('react-redux').connect

var mapStateToProps = function (state) {
  return {
    value: state,
  }
}

var mapDispatchToProps = function (dispatch) {
  return {
    plus: function () {
      dispatch({
        type: 'PLUS',
        value: 2,
      })
    },
    minus: function () {
      dispatch({
        type: 'MINUS',
        value: 2,
      })
    },
  }
}

var CounterContainer = connect(mapStateToProps, mapDispatchToProps)(Counter)

module.exports = CounterContainer

5.Split actions

上面的例子已经很接近 React 的初级 App 的设计了,但当我们的 Component 特别复杂时,往往 action 也会难抽象,像上面的dispatch({type: "PLUS", value: 2});偶合度太高,因为我们根本不知道这个 action 为什么是这样,就好比我们随便写了一个常量而并未定义任何变量名一样,别人是很难阅读的。因此比较好的做法是把 action 更小的分离,比如上面的 action,我们可以分离成如下:

module.exports.plusAction = function (val) {
  return {
    type: 'PLUS',
    value: val,
  }
}

module.exports.minusAction = function (val) {
  return {
    type: 'MINUS',
    value: val,
  }
}

这样,在 dispatch 时,也会显得很简洁。

6.ES6 refactor

ES6 部分就不在这里赘述了,大多都是语法问题,建议大家可以参考阮老师的书 ES6 入门

7.写在最后

个人认为,学习 React 十分不推荐一上手就用各种 module,或者照猫画虎式的去填空,这样只能是到头来什么也不会。当你从头开始去理解时,才能找到痛点,而当你有痛点时你才需要重构,那么此时可能某个 module 就是你想要的。你用它只是为了省时,而不是你做不出来才用它。借用我前几天在知乎上回答的问题“用库丢脸不?”,我的观点是:用库不丢脸,不懂库还非要用库才丢脸