React从零学起

原文请戳

初接触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打包原理不在这里赘述。

MyCounter.js

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就是你想要的。你用它只是为了省时,而不是你做不出来才用它。借用我前几天在知乎上回答的问题“用库丢脸不?”,我的观点是:用库不丢脸,不懂库还非要用库才丢脸原文请戳

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容