2017年2月18日 星期六

Redux的createStore實作

建立一個計數器範例,點擊頁面會執行累加計數
直接使用 createStore:
import { createStore } from 'redux';

const counter = (state = 0, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

const store = createStore(counter);
console.log(`initial state : ${store.getState()}`);

store.subscribe(() => {
    document.body.innerHTML = store.getState();
})

document.addEventListener('click', () => {
    store.dispatch({ type: 'INCREMENT' });

});

現在 counter 方法直接沿用,另外建立一個 createStore 方法:
const createStore = (reducer) => {
    // 這個 store 持有 state 變數
    let state;

    const getState = () => state;

    const dispatch = (action) => {

    };

    const subscribe = (listener) => {

    };

    // 回傳的物件被稱為 Redux store
    return { getState, dispatch, subscribe }

};

因為 subscribe() 可以被呼叫很多次,所以需要紀錄這些 listener:
    let listeners = [];

    const subscribe = (listener) => {
        listeners.push(listener)
    };

dispatch() 是唯一可以改變內部 state 的:
    const dispatch = (action) => {
        // 使用當前的 state 和 被 dispatch 的 action 物件當參數呼叫 reducer 計算出新的 state
        state = reducer(state, action);
        // 執行 listeners
        listeners.forEach((listener) => { listener() });
    };

還沒有實作 unsubscribe() 方法,先使用替代方案,在subscribe() 寫一個回傳一個方法:
        // 回傳一個方法
        // 使用方式:
        //     var s1 = store.subscribe(()=>{});
        //     s1(); // unsubscribe
        return () => {
            listeners = listeners.filter(l => l !== listener);
        }

最後在 createStore() 回傳前加入 dispatch({});
為了得到初始 state

createStore 完整範例:
const createStore = (reducer) => {
    let state;
    let listeners = [];

    const getState = () => state;

    const dispatch = (action) => {
        state = reducer(state, action);
        listeners.forEach((listener) => { listener() });
    };

    const subscribe = (listener) => {
        listeners.push(listener);
        return () => {
            listeners = listeners.filter(l => l !== listener);
        }
    };

    dispatch({});

    return { getState, dispatch, subscribe }

};










2017年2月13日 星期一

用最少的套件安裝React應用程式並啟動

1.  建立資料夾,在該資料下開啟終端機並執行:
    $ npm init -y
 
    參考:
    https://docs.npmjs.com/cli/init

2. 安裝 React 套件
    $ npm install --save react react-dom

3. 安裝轉譯套件
    $ npm install --global babel-cli
    $ npm install --save-dev babel-preset-es2015 babel-preset-react

4. 建立 .babelrc
    {
        "presets" : ["es2015","react"]
    }

5. 安裝 webpack
    $ npm install --save-dev webpack

6. 建立 webpack.config.js
var path = require('path');

module.exports = {
  entry: './app/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};    
    參考:https://webpack.js.org/guides/get-started/

7. 執行 babel 和 webpack
    $ babel js/source -d js/build --watch
    $ webpack --config webpack.config.js --watch --colors --progress -d
    --watch : 程式有異動就執行webpack
    --colors : 顯示一些顏色
    --progress : 顯示執行進度
    -d : 加入 Source Map (方便 debug)
    -p : production code (不斷行也不空白的很醜的程式碼)

後續:
想使用babel-loader卻一直失敗,後來發現網路上很多教學文章都是用webpack 1,而我安裝的是2,
配置React的Babel和Webpack2環境:
https://segmentfault.com/a/1190000007000131
https://blog.madewithenvy.com/getting-started-with-webpack-2-ed2b86c68783#.zg5j3c46z
https://www.smashingmagazine.com/2017/02/a-detailed-introduction-to-webpack/?utm_source=javascriptweekly&utm_medium=email
簡單設定:
module.exports = {
    // ...其他設定
    module : {
        rules : [
            {
                test : /\.js$/,
                use : 'babel-loader',
                exclude : /node_modules/
            }
        ]
    }
}
指定路徑下的終端機輸入
$ code .
會運行Visual Studio Code並開啟指定目錄