Home Thinking In React Way - 有限状态机
Post
Cancel

Thinking In React Way - 有限状态机

Thinking In React Way - 有限状态机

今天来聊聊前端,其实这个公众号刚开始写起来的时候,是想和大家分享数据可视化的一些知识的。这也是图表君名头的由来,后来慢慢就写进了些前端的东西,再后来,因为图表君工作的转换,现在更多的做一些后台的工作,那么讨论的问题就更杂了,现在看起来算是我的一些工作和学习心得和感受吧。

好了,说了这么多。今天聊点什么呢?聊聊React。其实这也并不是什么新东西了。2014年,图表君就知道有这么个东西,听着几位业界大牛聊,说这东西有多么多么的好,当时也不是特别的理解,跟了一遍官方的Tutorial,也没有特别的感受。应该是因为当时前端的经验特别的浅(虽然现在也不敢说深,捂脸),没有什么特别的感受吧。

Angular的痛

后来做了几个项目,使用的是Angular,刚开始觉得双向数据绑定好牛逼啊,好好用,好好用。但是随着项目逐渐的一点点变大,感觉$scope上的东西是越来越多,各个controller里各有各的$scope,再加上$scope是继承的,当项目一复杂就越来越难以管理和控制。还有自己要封装一个组件在angular里得用directive吧,好吧看看directive的文档你就得晕了,link,compile,controller都是什么鬼。好了,今天不是吐槽angular的时间,但是angular得设计和使用的确是太复杂了。

前端到底是在干什么

好了,让我们先暂时跳出框架的讨论,来思考一下,前端的工作到底是干什么的?其实可以简单的说就是将数据到View的一个映射上,也就是说无论什么框架解决的基本问题就是讲数据展示到View上, 然后将讲用户的操作最后再反应到数据的变化上来。

1
2
3
4
5
6
7

					Data --> Whatever FrameWork --> View
					Data <-- Whatever FrameWork <-- View



再想清楚这个问题之后,React是怎么做的呢?React并不是一个完整的前端框架,只是一个专注于渲染View的library,在看了React的文档之后,我们会发现他的api是很简单的。一个典型的react的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// Example usage: <ShoppingList name="Mark" />

即使你没有React的经验,看这样的代码也不会有什么特别的问题。好了,今天图表君不打算安利React,并不想写一个hello world出来。这样的文章太多,看看React的官方例子会比图表君写的好很多。那么今天说什么呢?

React Thinking - 状态机

看这部分之前,图表君强烈建议你可以看一看React的官方tutorialtutorial,很好的一个例子,也不长。一个小时就能看完,自己上手写一写,感受会更深。好了现在假设你看完了这个tutorial有什么感觉?

图表君的最大的感受是,最后将State,function都定义到了Game的这个Root级别的Component上了,再把所有的数据和function都传进自己的子Component里,需要的地方直接调用就好了。这样就使得我们上边说把Data的操作逻辑都被提出来,并集中在一起了,一下就清晰了,明确了。App管理从此变得一下简单了。反复品味这样的设计,忽然有个东西,进入了我的思维里。这东西不就是一个有限状态机呀。

有限状态机

有限状态机是个十分有用的模型,可以用来模拟世界上大部分的事物,其有三个特征:

  1. 状态总数(state)是有限的。
  2. 任一时刻,只处在一种状态之中。
  3. 某种条件下,会从一种状态转变(transition)到另一种状态。

我们再来看看例子中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Game extends React.Component {
  constructor(){
    super();
    this.state={
      history:[{
        squares: Array(9).fill(null)
      }],
      stepNumber: 0,
      xIsNext: true
    }
  }
  
  handleClick(i){
    const history = this.state.history;
    const stepNumber = this.state.stepNumber
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext? 'X':'O';
    this.setState(
      {
        history: history.concat([{
      squares: squares   
    }]),
        stepNumber: stepNumber + 1,
    xIsNext: !this.state.xIsNext,
      }
    )
  }
  
  jumpTo(step){
    const newHistory = this.state.history.slice(0,step+1)
    console.log(newHistory);
    this.setState({
      history: newHistory,
      stepNumber:step,
      xIsNext: (step % 2) ? false: true, 
    })
  }
  
  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares);
    let status;
    if(winner){
       status = 'Winner Is :' + winner;
    }else{
       status = 'Next player: ' + (this.state.xIsNext ? 'X':'O');
    }
    const moves = history.map((step,move) => {
       const desc = move ? 'Move #' + move : 'Game Start';
       return(
         <li key={move}>
            <a href="#" onClick={() => this.jumpTo(move)}>{desc}</a>
         </li> 
       )
    });
    return (
      <div className="game">
        <div className="game-board">
          <Board squares={current.squares} onClick={(i) => this.handleClick(i)} />
        </div>
        <div className="game-info">
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }
}

构造方法,constructor - 定义了APP的初始状态。

handleClick - 定义了在棋盘里点击事件后的APP状态的变化。

jumpTo - 定义点击历史记录中某一步后APP的状态变化。

render- 描述如何在View上来展示当前的状态。

这样精巧的设计,facebook果然聚集了当今世界一流的工程师。然后我看了阮一峰的这篇介绍有限状态机的文章,看到这段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var menu = {
      
    // 当前状态
    currentState: 'hide',
  
    // 绑定事件
    initialize: function() {
      var self = this;
      self.on("hover", self.transition);
    },
  
    // 状态转换
    transition: function(event){
      switch(this.currentState) {
        case "hide":
          this.currentState = 'show';
          doSomething();
          break;
        case "show":
          this.currentState = 'hide';
          doSomething();
          break;
        default:
          console.log('Invalid State!');
          break;
      }
    }
  
  };
  

有没有似曾相识的感觉,Redux里是不是就是这么干的?然后再想想Redux,到底干了一件什么事?帮我们做了这样的一个状态机啊,我们开发者只要定义Action,Reducer,他把我们的APP组织成了一个状态机。

从这样的角度再来看React,Redux这个的技术栈,我觉得理解的更加的清楚了,当然这仅仅是我的一点点小小的思考,欢迎大家一起讨论拍砖,后边逐步的和大家分享我的心得体会。

This post is licensed under CC BY 4.0 by the author.

我们在ThoughtWorks打造一款P3产品

骚窝点滴,一个新人的年终感悟

Comments powered by Disqus.