成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

基于Redux架構(gòu)的單頁(yè)應(yīng)用開發(fā)總結(jié)

fish / 2976人閱讀

摘要:系統(tǒng)架構(gòu)介紹本項(xiàng)目開發(fā)基于框架,利用進(jìn)行模塊化構(gòu)建,前端編寫語(yǔ)言是,利用進(jìn)行轉(zhuǎn)換。單頁(yè)是為單頁(yè)應(yīng)用量身定做的你可以把拆成很多,這些由路由來(lái)加載。前者用來(lái)獲取的狀態(tài),后者用來(lái)修改的狀態(tài)。

系統(tǒng)架構(gòu)介紹

本項(xiàng)目開發(fā)基于 React + Redux + React-Route 框架,利用 webpack 進(jìn)行模塊化構(gòu)建,前端編寫語(yǔ)言是 JavaScript ES6,利用 babel進(jìn)行轉(zhuǎn)換。

|--- project
        |--- build                    // 項(xiàng)目打包編譯目錄
        |--- src                      // 項(xiàng)目開發(fā)的源代碼
            |--- actions              // redux的動(dòng)作
            |--- components           // redux的組件
            |--- containers           // redux的容器  
            |--- images               // 靜態(tài)圖片
            |--- mixins               // 通用的函數(shù)庫(kù)
            |--- reducers             // redux的store操作
            |--- configureStore.js    // redux的store映射
            |--- index.js             // 頁(yè)面入口
            |--- routes.js            // 路由配置
        |--- index.html               // 入口文件
        |--- .babelrc                 // babel配置
        |--- main.js                  // webkit打包的殼子
        |--- package.json             // 包信息
        |--- webpack.config.js        // webpack配置文件
        |--- readme.md           
"dependencies": {
    "babel-polyfill": "^6.7.4",
    "base-64": "^0.1.0",
    "immutable": "^3.7.6",
    "isomorphic-fetch": "^2.2.1",
    "moment": "^2.13.0",
    "normalizr": "^2.0.1",
    "react": "^0.14.8",
    "react-datetimepicker": "^2.0.0",
    "react-dom": "^0.14.8",
    "react-redux": "^4.4.1",
    "react-redux-spinner": "^0.4.0",
    "react-router": "^2.0.1",
    "react-router-redux": "^4.0.1",
    "redux": "^3.3.1",
    "redux-immutablejs": "0.0.8",
    "redux-logger": "^2.6.1",
    "redux-thunk": "^2.0.1"
  },
  "devDependencies": {
    "babel-core": "^6.7.5",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "babel-preset-stage-1": "^6.5.0",
    "css-loader": "^0.23.1",
    "file-loader": "^0.8.5",
    "img-loader": "^1.2.2",
    "less": "^2.6.1",
    "less-loader": "^2.2.3",
    "mocha": "^2.4.5",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.14"
  }
webpack配置

也算是實(shí)際體驗(yàn)了一把webpack,不得不說(shuō),論React最佳搭檔,非此貨莫屬!真的很強(qiáng)大,很好用。

var webpack = require("webpack");   // 引入webpack模塊
var path = require("path");         // 引入node的path模塊
var nodeModulesPath = path.join(__dirname, "/node_modules");  // 設(shè)置node_modules目錄

module.exports = {
    // 配置入口(此處定義了雙入口)
    entry: {
        bundle: "./src/index",
        vendor: ["react", "react-dom", "redux"]
    },
    // 配置輸出目錄
    output: {
        path: path.join(__dirname, "/build"),
        publicPath: "/assets/",
        filename: "bundle.js"
    },
    module: {
        noParse: [
            path.join(nodeModulesPath, "/react/dist/react.min"),
            path.join(nodeModulesPath, "/react-dom/dist/react-dom.min"),
            path.join(nodeModulesPath, "/redux/dist/redux.min"),
        ],
        // 加載器
        loaders: [
            // less加載器
            { test: /.less$/, loader: "style!css!less" },
            // babel加載器
            { test: /.js$/, exclude: /node_modules/, loader: "babel-loader" },
            // 圖片加載器(圖片超過(guò)8k會(huì)自動(dòng)轉(zhuǎn)base64格式)
            { test: /.(gif|jpg|png)$/, loader: "url?limit=8192&name=images/[name].[hash].[ext]"},
            // 加載icon字體文件
            { test: /.(woff|svg|eot|ttf)$/, loader: "url?limit=50000&name=fonts/[name].[hash].[ext]"}
        ]
    },
    // 外部依賴(不會(huì)打包到bundle.js里)
    externals: { 
        "citys": "Citys"
    },
    // 插件
    plugins: [
        //new webpack.HotModuleReplacementPlugin(),  // 版本上線時(shí)開啟
        new webpack.DefinePlugin({
            // 定義生產(chǎn)環(huán)境
            "process.env": {
                NODE_ENV: JSON.stringify("production")
            }
        }),
        //new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), // 版本上線時(shí)開啟
        // 公共部分會(huì)被抽離到vendor.js里
        new webpack.optimize.CommonsChunkPlugin("vendor",  "vendor.js"),
        // 比對(duì)id的使用頻率和分布來(lái)得出最短的id分配給使用頻率高的模塊
        new webpack.optimize.OccurenceOrderPlugin(),
        // 允許錯(cuò)誤不打斷程序
        new webpack.NoErrorsPlugin()
    ],
};
延伸-Webpack性能優(yōu)化 最小化

為了瘦身你的js(還有你的css,如果你用到css-loader的話)webpack支持一個(gè)簡(jiǎn)單的配置項(xiàng):

new webpack.optimize.UglifyJsPlugin()

這是一種簡(jiǎn)單而有效的方法來(lái)優(yōu)化你的webapp。而webpack還提供了modules 和 chunks ids 來(lái)區(qū)分他們倆。利用下面的配置項(xiàng),webpack就能夠比對(duì)id的使用頻率和分布來(lái)得出最短的id分配給使用頻率高的模塊。

new webpack.optimize.OccurenceOrderPlugin()

入口文件對(duì)于文件大小有較高的優(yōu)先級(jí)(入口文件壓縮優(yōu)化率盡量的好)

去重

如果你使用了一些有著很酷的依賴樹的庫(kù),那么它可能存在一些文件是重復(fù)的。webpack可以找到這些文件并去重。這保證了重復(fù)的代碼不被大包到bundle文件里面去,取而代之的是運(yùn)行時(shí)請(qǐng)求一個(gè)封裝的函數(shù)。不會(huì)影響語(yǔ)義

new webpack.optimize.DedupePlugin()

這個(gè)功能可能會(huì)增加入口模塊的一些花銷

對(duì)于chunks的優(yōu)化

當(dāng)coding的時(shí)候,你可能已經(jīng)添加了許多分割點(diǎn)來(lái)按需加載。但編譯完了之后你發(fā)現(xiàn)有太多細(xì)小的模塊造成了很大的HTTP損耗。幸運(yùn)的是Webpack可以處理這個(gè)問(wèn)題,你可以做下面兩件事情來(lái)合并一些請(qǐng)求:

Limit the maximum chunk count with

new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15})

Limit the minimum chunk size with

new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000})

Webpack通過(guò)合并來(lái)管理這些異步加載的模塊(合并更多的時(shí)候發(fā)生在當(dāng)前這個(gè)chunk有復(fù)用的地方)。文件只要在入口頁(yè)面加載的時(shí)候沒(méi)有被引入,那么就不會(huì)被合并到chunk里面去。

單頁(yè)

Webpack 是為單頁(yè)應(yīng)用量身定做的 你可以把a(bǔ)pp拆成很多chunk,這些chunk由路由來(lái)加載。入口模塊僅僅包含路由和一些庫(kù),沒(méi)有別的內(nèi)容。這么做在用戶通過(guò)導(dǎo)航瀏覽表現(xiàn)很好,但是初始化頁(yè)面加載的時(shí)候你需要2個(gè)網(wǎng)絡(luò)請(qǐng)求:一個(gè)是請(qǐng)求路由,一個(gè)是加載當(dāng)前內(nèi)容。

如果你利用HTML5的HistoryAPI 來(lái)讓URL影響當(dāng)前內(nèi)容頁(yè)的話。你的服務(wù)器可以知道那個(gè)內(nèi)容頁(yè)面將被客戶端請(qǐng)求。為了節(jié)約請(qǐng)求數(shù),服務(wù)端可以把要請(qǐng)求的內(nèi)容模塊放到響應(yīng)頭里面:以script標(biāo)簽的形式來(lái)添加,瀏覽器將并行的加載這倆請(qǐng)求。


你可以從build stas里面提取出chunk的filename (stats-webpack-plugin )

多頁(yè)

當(dāng)編譯一個(gè)多頁(yè)面的app時(shí),你想要在頁(yè)面之間共享一些代碼。這在webpack看來(lái)很簡(jiǎn)單的:只需要和多個(gè)入口文件一起編譯就好

webpack p1=./page1 p2=./page2 p3=./page3 [name].entry-chunk.js
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3"
    },
    output: {
        filename: "[name].entry.chunk.js"
    }
}

由上面可以產(chǎn)出多個(gè)入口文件

p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js

但是可以增加一個(gè)chunk來(lái)共享她們中的一些代碼。 如果你的chunks有一些公用的modules,那我推薦一個(gè)很酷的插件CommonsChunkPlugin,它能辨別共用模塊并把他們放倒一個(gè)文件里面去。你需要在你的頁(yè)面里添加兩個(gè)script標(biāo)簽來(lái)分別引入入口文件和共用模塊文件。

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3"
    },
    output: {
        filename: "[name].entry.chunk.js"
    },
    plugins: [
        new CommonsChunkPlugin("commons.chunk.js")
    ]
}

由上面可以產(chǎn)出入口文件

p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js

和共用文件

commons.chunk.js

在頁(yè)面中要首先加載 commons.chunk.js 在加載xx.entry.chunk.js 你可以出實(shí)話很多個(gè)commons chunks ,通過(guò)選擇不同的入口文件。并且你可以堆疊使用這些commons chunks。

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3",
        ap1: "./admin/page1",
        ap2: "./admin/page2"
    },
    output: {
        filename: "[name].js"
    },
    plugins: [
        new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]),
        new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"])
    ]
};

輸出結(jié)果:

page1.html: commons.js, p1.js
page2.html: commons.js, p2.js
page3.html: p3.js
admin-page1.html: commons.js, admin-commons.js, ap1.js
admin-page2.html: commons.js, admin-commons.js, ap2.js

另外你可以將多個(gè)共用文件打包到一個(gè)共用文件中。

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        commons: "./entry-for-the-commons-chunk"
    },
    plugins: [
        new CommonsChunkPlugin("commons", "commons.js")
    ]
};
關(guān)于less的組織

作為一個(gè)后端出身的前端工程師,寫簡(jiǎn)單的css實(shí)在沒(méi)有那種代碼可配置和結(jié)構(gòu)化的快感。所以引入less是個(gè)不錯(cuò)的選擇,無(wú)論是針對(duì)代碼后期的管理,還是提高代碼的復(fù)用能力。

global.less

這個(gè)是全局都可以調(diào)用的方法庫(kù),我習(xí)慣把 項(xiàng)目的配色、各種字號(hào)、用于引入混出的方法等寫在這里,其他container頁(yè)面通過(guò)@import方式引入它,就可以使用里面的東西。不過(guò)定義它時(shí)要注意以下兩點(diǎn):

第一,這個(gè)less里只能存放變量和方法,less編譯時(shí)會(huì)忽略它們,只在調(diào)用它們的地方才編譯成css。所以為了防止代碼重復(fù),請(qǐng)不要在這里直接定義樣式,而是用一個(gè)方法把它們包起來(lái),表示一個(gè)用途。

第二,這個(gè)less里的方法如果是針對(duì)某些具體標(biāo)簽定義樣式的,只能初始化一次,建議在單頁(yè)的入口container里做,這樣好維護(hù)。比如reset()(頁(yè)面標(biāo)簽樣式初始化),這個(gè)方法放在入口containerlogin.less里調(diào)用且全局只調(diào)用一次。

下面是我的global.less 常用的一些模塊

/**
 * @desc 一些全局的less
 * @createDate 2016-05-16
 * @author Jafeney <[email protected]>
 **/

// 全局配色
@g-color-active: #ff634d;  //活躍狀態(tài)的背景色(橘紅色)
@g-color-info: #53b2ea;    //一般用途的背景色(淺藍(lán)色)
@g-color-primary: #459df5; //主要用途的背景色 (深藍(lán)色)
@g-color-warning: #f7cec8; //用于提示的背景色 (橘紅色較淺)
@g-color-success: #98cf07; //成功狀態(tài)的背景色 (綠色)
@g-color-fail: #c21f16;    //失敗狀態(tài)的背景色 (紅色)
@g-color-danger: #ff634d;  //用于警示的背景色 (橘紅色)
@g-color-light: #fde2e1;   //高飽合度淡色的背景色(橘紅)

// 全局尺寸
@g-text-default: 14px;
@g-text-sm: 12px;
@g-text-lg: 18px;

// 全局使用的自定義icon(這樣寫的好處是webpack打包時(shí)自動(dòng)轉(zhuǎn)base64)
@g-icon-logo: url("../images/logo.png");
@g-icon-logoBlack: url("../images/logoBlack.png");
@g-icon-phone: url("../images/phone.png");
@g-icon-message: url("../images/message.png");
@g-icon-help: url("../images/help.png");
@g-icon-down: url("../images/down.png");
@g-icon-top: url("../images/top.png");
@g-icon-home: url("../images/home.png");
@g-icon-order: url("../images/order.png");
@g-icon-cart: url("../images/cart.png");
@g-icon-source: url("../images/source.png");
@g-icon-business: url("../images/business.png");
@g-icon-finance: url("../images/finance.png");
@g-icon-account: url("../images/account.png");
// ....

// 背景色
@g-color-grey1: #2a2f33;   //黑色
@g-color-grey2: #363b3f;   //深灰色
@g-color-grey3: #e5e5e5;   //灰色
@g-color-grey4: #efefef;   //淺灰色
@g-color-grey5: #f9f9f9;   //很淺
@g-color-grey6: #ffffff;   //白色

// 全局邊框
@g-border-default: #e6eaed;
@g-border-active: #53b2ea;
@g-border-light: #f7dfde;

// 常用的border-box盒子模型
.border-box() {
    box-sizing: border-box;
    -ms-box-sizing: border-box;
    -moz-box-sizing: border-box;
    -o-box-sizing: border-box;
    -webkit-box-sizing: border-box;
}

// 模擬按鈕效果
.btn() {
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;

    &:hover {
        opacity: .8;
    }

    &.disabled {
        &:hover {
            opacity: 1;
            cursor: not-allowed;
        }
    }
}

// 超出部分處理
.text-overflow() {
    overflow: hidden;
    text-overflow: ellipsis;
    -o-text-overflow: ellipsis;
    -webkit-text-overflow: ellipsis;
    -moz-text-overflow: ellipsis;
    white-space: nowrap;
}

// reset styles
.reset() {
// ....
}

// 一些原子class
.atom() {
    .cp {
        cursor: pointer;
    }
    .ml-5 {
        margin-left: 5px;
    }
    .mr-5 {
        margin-right: 5px;
    }
    .ml-5p {
        margin-left: 5%;
    }
    .mr-5p {
        margin-right: 5%;
    }
    .mt-5 {
        margin-top: 5px;
    }

    .txt-center {
        text-align: center;
    }
    .txt-left {
        text-align: left;
    }
    .txt-right {
        text-align: right;
    }
    .fr {
        float: right;
    }
    .fl {
        float: left;
    }
}
component的less

為了降低組件的耦合性,每個(gè)組件的less必須多帶帶寫,樣式跟著組件走,一個(gè)組件一個(gè)less,不要有其他依賴,保證組件的高移植能力。
而且組件應(yīng)該針對(duì)用途提供幾套樣式方案,比如button組件,我們可以針對(duì)顏色提供不同的樣式,以樣式組合的方式提供給外部使用。

// 下面的變量可以針對(duì)不同的需求進(jìn)行配置
@color-primary: #459df5; 
@color-warning: #f7cec8; 
@color-success: #98cf07; 
@color-fail: #c21f16;    

.btn {
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    display: inline-block;
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    -ms-box-sizing: border-box;
    -moz-box-sizing: border-box;
    -o-box-sizing: border-box;
    text-align: center;
    
    // 鼠標(biāo)放上時(shí)
    &:hover {
        opacity: .8;
    }
    
    // 按鈕不可用時(shí)
    &.disabled {
        &:hover {
            opacity: 1;
            cursor: not-allowed;
        }
    }
    
    // 填充式按鈕
    &.full {
        color: #fff;
        &.primary {
            background-color:  @color-primary;
            border: 1px solid @color-primary;
        }
        // ....
    }

    // 邊框式按鈕 
    &.border {
       background-color:  #fff;
       &.primary {
            color: @color-primary;
            border: 1px solid @color-primary;
        }
        // ...
    }
}
container的less

同上,每個(gè)container一個(gè)less文件,可以復(fù)用的模塊盡量封裝成component,而不是偷懶復(fù)制幾行樣式過(guò)來(lái),這樣雖然方便一時(shí),但隨著項(xiàng)目的迭代,后期的冗余代碼會(huì)多得超出你的想象。
如果遵循組件化的設(shè)計(jì)思想,你會(huì)發(fā)現(xiàn)container里其實(shí)只有一些布局和尺寸定義相關(guān)的代碼,非常容易維護(hù)。

這是大型項(xiàng)目的設(shè)計(jì)要領(lǐng),除此之外就是大局觀的培養(yǎng),這點(diǎn)尤為重要,項(xiàng)目一拿來(lái)不要馬上就動(dòng)手寫頁(yè)面,而是應(yīng)該多花些時(shí)間在代碼的設(shè)計(jì)上,把全局的東西剝離出來(lái),越細(xì)越好;把可復(fù)用的模塊設(shè)計(jì)成組件,思考組件的拓展性和不同的用途,記住—— 結(jié)構(gòu)上盡量減少依賴關(guān)系,保持組件的獨(dú)立性,而用途上多考慮功能的聚合,即所謂的低耦合高聚合。

不過(guò)實(shí)際項(xiàng)目不可能每個(gè)組件都是獨(dú)立存在的,有時(shí)我們?yōu)榱诉M(jìn)一步減少代碼量,會(huì)把一些常用的組件整合成一個(gè)大組件來(lái)使用,即復(fù)合組件。所以每個(gè)項(xiàng)目實(shí)際上存在一級(jí)組件(獨(dú)立)和二級(jí)組件(復(fù)合)。一級(jí)組件可以隨意遷移,而二級(jí)組件是針對(duì)實(shí)際場(chǎng)景而生的,兩者并沒(méi)有好壞之分,一切都為了高效地生產(chǎn)代碼,存在即合理。

關(guān)于React的組織

本項(xiàng)目的React代碼都用JavaScript的ES6風(fēng)格編寫,代碼非常地優(yōu)雅,而且語(yǔ)言自身支持模塊化,再也不用依賴BrowserifyRequireJS等工具了,非常爽。如果你不會(huì)ES6,建議去翻一翻阮一峰老師的《ES6標(biāo)準(zhǔn)入門》

入口

入口模塊index.js放在src的根目錄,是外部調(diào)用的入口。

import React from "react"
import { render } from "react-dom"
// 引入redux
import { Provider } from "react-redux"
// 引入router
import { Router, hashHistory } from "react-router"
import { syncHistoryWithStore } from "react-router-redux"
import routes from "./routes"
import configureStore from "./configureStore"

const store = configureStore(hashHistory)  // 路由的store
const history = syncHistoryWithStore(hashHistory, store) // 路由的歷史紀(jì)錄(會(huì)寫入到瀏覽器的歷史紀(jì)錄)

render(
  (
  
    
  
  ), document.getElementById("root")
)
路由

這里主要應(yīng)用了react-route組件來(lái)制作哈希路由,使用方式很簡(jiǎn)單,和ReactNative里的Navigator組件類似。

import React from "react"
import { Route } from "react-router"

import Manager from "./containers/manager"

import Login from "./containers/Login/"
import Register from "./containers/Register/"
import Password from "./containers/Password/"
import Dashboard from "./containers/Dashboard/"

const routes = (
  
                                    // 主容器
                               // 儀表盤
        // .... 各模塊的container
    
                               // 登錄
                         // 注冊(cè)
                         // 找回密碼
  
)

export default routes
了解action、store、reducer

從調(diào)用關(guān)系來(lái)看如下所示:

store.dispatch(action) --> reducer(state, action) --> final state

來(lái)個(gè)實(shí)際的例子:

// reducer方法, 傳入的參數(shù)有兩個(gè)
// state: 當(dāng)前的state
// action: 當(dāng)前觸發(fā)的行為, {type: "xx"}
// 返回值: 新的state
var reducer = function(state, action){
    switch (action.type) {
        case "add_todo":
            return state.concat(action.text);
        default:
            return state;
    }
};

// 創(chuàng)建store, 傳入兩個(gè)參數(shù)
// 參數(shù)1: reducer 用來(lái)修改state
// 參數(shù)2(可選): [], 默認(rèn)的state值,如果不傳, 則為undefined
var store = redux.createStore(reducer, []);

// 通過(guò) store.getState() 可以獲取當(dāng)前store的狀態(tài)(state)
// 默認(rèn)的值是 createStore 傳入的第二個(gè)參數(shù)
console.log("state is: " + store.getState());  // state is:

// 通過(guò) store.dispatch(action) 來(lái)達(dá)到修改 state 的目的
// 注意: 在redux里,唯一能夠修改state的方法,就是通過(guò) store.dispatch(action)
store.dispatch({type: "add_todo", text: "讀書"});
// 打印出修改后的state
console.log("state is: " + store.getState());  // state is: 讀書

store.dispatch({type: "add_todo", text: "寫作"});
console.log("state is: " + store.getState());  // state is: 讀書,寫作
store、reducer、action關(guān)聯(lián)

store:對(duì)flux有了解的同學(xué)應(yīng)該有所了解,store在這里代表的是數(shù)據(jù)模型,內(nèi)部維護(hù)了一個(gè)state變量,用例描述應(yīng)用的狀態(tài)。store有兩個(gè)核心方法,分別是getState、dispatch。前者用來(lái)獲取store的狀態(tài)(state),后者用來(lái)修改store的狀態(tài)。

// 創(chuàng)建store, 傳入兩個(gè)參數(shù)
// 參數(shù)1: reducer 用來(lái)修改state
// 參數(shù)2(可選): [], 默認(rèn)的state值,如果不傳, 則為undefined
var store = redux.createStore(reducer, []);

// 通過(guò) store.getState() 可以獲取當(dāng)前store的狀態(tài)(state)
// 默認(rèn)的值是 createStore 傳入的第二個(gè)參數(shù)
console.log("state is: " + store.getState());  // state is:

// 通過(guò) store.dispatch(action) 來(lái)達(dá)到修改 state 的目的
// 注意: 在redux里,唯一能夠修改state的方法,就是通過(guò) store.dispatch(action)
store.dispatch({type: "add_todo", text: "讀書"});

action:對(duì)行為(如用戶行為)的抽象,在redux里是一個(gè)普通的js對(duì)象。redux對(duì)action的約定比較弱,除了一點(diǎn),action必須有一個(gè)type字段來(lái)標(biāo)識(shí)這個(gè)行為的類型。所以,下面的都是合法的action

{type:"add_todo", text:"讀書"}
{type:"add_todo", text:"寫作"}
{type:"add_todo", text:"睡覺(jué)", time:"晚上"}

reducer:一個(gè)普通的函數(shù),用來(lái)修改store的狀態(tài)。傳入兩個(gè)參數(shù) state、action。其中,state為當(dāng)前的狀態(tài)(可通過(guò)store.getState()獲得),而action為當(dāng)前觸發(fā)的行為(通過(guò)store.dispatch(action)調(diào)用觸發(fā))。reducer(state, action) 返回的值,就是store最新的state值。

// reducer方法, 傳入的參數(shù)有兩個(gè)
// state: 當(dāng)前的state
// action: 當(dāng)前觸發(fā)的行為, {type: "xx"}
// 返回值: 新的state
var reducer = function(state, action){
    switch (action.type) {
        case "add_todo":
            return state.concat(action.text);
        default:
            return state;
    }
}
React式編程思維

在沒(méi)有遁入React之前,我是一個(gè)DOM操作控,不論是jQuery還是zepto,我在頁(yè)面交互的實(shí)現(xiàn)上用的最多的就是DOM操作,把復(fù)雜的交互一步一步通過(guò)選擇器和事件委托綁定到document上,然后逐個(gè)連貫起來(lái)。

$(document).on("event", "element", function(e){
    e.preventDefault();
    var that = this;
    var parent = $(this).parent();
    var siblings = $(this).siblings();
    var children = $(this).children();
    // .....
});

這是jQuery式的編程思維,React和它截然不同。React的設(shè)計(jì)是基于組件化的,每個(gè)組件通過(guò)生命周期維護(hù)統(tǒng)一的state,state改變,組件便update,重新觸發(fā)render,即重新渲染頁(yè)面。而這個(gè)過(guò)程操作的其實(shí)是內(nèi)存里的虛擬DOM,而不是真正的DOM節(jié)點(diǎn),加上其內(nèi)部的差異更新算法,所以性能上比傳統(tǒng)的DOM操作要好。

舉個(gè)簡(jiǎn)單的例子:

現(xiàn)在要實(shí)現(xiàn)一個(gè)模態(tài)組件,如果用jQuery式的編程思維,很習(xí)慣這么寫:

/**
 * @desc 全局模態(tài)窗口
 **/
var $ = window.$;
var modal = {
    confirm: function(opts) {
        var title = opts.title || "提示",
            content = opts.content || "提示內(nèi)容",
            callback = opts.callback;
        var newNode = [
            "
", "", "
", ].join(""); $("#J_mask").remove(); $("body").append(newNode); $("#J_cancel").on("click", function() { $("#J_mask").remove(); }); $("#J_confirm").on("click", function() { if (typeof callback === "function") { callback(); } $("#J_mask").remove(); }); } }; module.exports = modal;

然后在頁(yè)面的JavaScript里通過(guò)選擇器觸發(fā)模態(tài)和傳遞參數(shù)。

var Modal = require("modal");
var $ = window.$;
var app = (function() {
    var init = function() {
        eventBind();
    };
    var eventBind = function() {
        $(document).on("click", "#btnShowModal", function() {
            Modal.confirm({
                title: "提示",
                content: "你好!世界",
                callback: function() {
                    console.log("Hello World");
                }
            });
        });
    };
    init();
})(); 

如果采用React式的編程思維,它應(yīng)該是這樣的:

/**
 * @desc 全局模態(tài)組件 Component
 * @author Jafeney
 * @createDate 2016-05-17
 * */
import React, { Component } from "react"
import "./index.less"

class Modal extends Component {
    constructor() {
        super()
        this.state = {
            jsMask: "mask hidden"
        }
    }
    show() {
        this.setState({
            jsMask: "mask"
        })
    }
    close() {
        this.setState({
            jsMask: "mask hidden"
        })
    }
    confirm() {
        this.props.onConfirm && this.props.onConfirm()
    }
     render() {
         return (
             

{ this.props.title }

this.close()}>
{ this.props.children }
this.confirm()}>{ this.props.confirmText || "確定" } { this.props.showCancel && (this.close()}>取消) }
); } } export default Modal

然后在containerrender()函數(shù)里通過(guò)標(biāo)簽的方式引入,并通過(guò)點(diǎn)擊觸發(fā)。

import {React, component} from "react"; 
import Modal from "Modal";

class App extends Component {
    render() {
       
} } export default App

你會(huì)發(fā)現(xiàn),上面的代碼并沒(méi)有刻意地操作某個(gè)DOM元素的樣式,而是通過(guò)改變組件的state去觸發(fā)自身的渲染函數(shù)。換句話說(shuō),我們不需要寫繁瑣的DOM操作,而是靠改變組件的state控制組件的交互和各種變化。這種思維方式的好處等你熟悉React之后自然會(huì)明白,可以大大地減少后期的代碼量。

優(yōu)化渲染

前面提到組件的state改變即觸發(fā)render(),React內(nèi)部雖然做了一些算法上的優(yōu)化,但是我們可以結(jié)合Immutable做進(jìn)一步的渲染優(yōu)化,讓頁(yè)面更新渲染速度變得更快。

/**
 * @desc PureRender 優(yōu)化渲染
 **/

import React, { Component } from "react"
import Immutable from "immutable";

export default {
    // 深度比較
    deepCompare: (self, nextProps, nextState) => {
        return !Immutable.is(self.props, nextProps) || !Immutable.is(self.state, nextState)
     },
    // 阻止沒(méi)必要的渲染
    loadDetection: (reducers=[])=> {
        for (let r of reducers) {
            if (!r.get("preload")) return (
) } } }

這樣我們?cè)?b>container的render()函數(shù)里就可以調(diào)用它進(jìn)行渲染優(yōu)化

import React, { Component } from "react"
import PureRenderMixin from "../../mixins/PureRender";

class App extends Component { 
    render() {
        let { actions, account, accountLogs, bankBind } = this.props;
        // 數(shù)據(jù)導(dǎo)入檢測(cè)
        let error = PureRenderMixin.loadDetection([account, accountLogs, bankBind])
        // 如果和上次沒(méi)有差異就阻止組件重新渲染
        if (error) return error   
        return (
            
// something ...
); } }
全局模塊的處理

其實(shí)Redux最大的作用就是有效減少代碼量,把繁瑣的操作通過(guò) action ----> reducer ----> store 進(jìn)行抽象,最后維護(hù)統(tǒng)一的state。對(duì)于頁(yè)面的全局模塊,簡(jiǎn)單地封裝成mixin來(lái)調(diào)用還是不夠的,比如全局的request模塊,下面介紹如何用Redux進(jìn)行改造。

首先在types.js里進(jìn)行聲明:

// request
export const REQUEST_PEDDING = "REQUEST_PEDDING";
export const REQUEST_DONE = "REQUEST_DONE";
export const REQUEST_ERROR = "REQUEST_ERROR";
export const REQUEST_CLEAN = "REQUEST_CLEAN";
export const REQUEST_SUCCESS = "REQUEST_SUCCESS";

然后編寫action:

/**
 * @desc 網(wǎng)絡(luò)請(qǐng)求模塊的actions
 **/

// fetch 需要使用 Promise 的 polyfill
import {
  pendingTask, // The action key for modifying loading state
  begin, // The action value if a "long" running task begun
  end // The action value if a "long" running task ended
} from "react-redux-spinner";
import "babel-polyfill"
import fetch from "isomorphic-fetch"
import Immutable from "immutable"
import * as CONFIG from "./config";   //請(qǐng)求的配置文件
import * as TYPES from "./types";

export function request(route, params, dispatch, success=null, error=null, { method="GET", headers={}, body=null } = {}) {
  dispatch({type: TYPES.REQUEST_PEDDING, [ pendingTask ]: begin})
  // 處理query
  const p = params ? "?" + Object.entries(params).map( (i)=> `${i[0]}=${encodeURI(i[1])}` ).join("&") : ""
  const uri = `${ CONFIG.API_URI }${ route }${ p }`
  let data = {method: method, headers: headers}
  if (method!="GET") data.body = body
  fetch(uri, data)
    .then((response) => {
      dispatch({type: TYPES.REQUEST_DONE, [ pendingTask ]: end})
      return response.json()
    })
    .then((data) => {
      if (String(data.code) == "0") {
        if (method !== "GET" ) dispatch({type: TYPES.REQUEST_SUCCESS});
        success && success(data);
      } else {
        console.log(data.error)
        dispatch({type: TYPES.REQUEST_ERROR, ...data})
        error && error(data)
      }
    })
    .catch((error) => {
        console.warn(error)
    })
}

export function requestClean() {
  return { type: TYPES.REQUEST_CLEAN }
}

然后編寫對(duì)應(yīng)的reducer操作state

import Immutable from "immutable";
import * as TYPES from "../actions/types";
import { createReducer } from "redux-immutablejs"

export default createReducer(Immutable.fromJS({status: null, error: null}), {
  [TYPES.REQUEST_ERROR]: (state, action) => {
    return state.merge({
        status: "error",
        code: action.code,
        error: Immutable.fromJS(action.error),
    })
  },
  [TYPES.REQUEST_CLEAN]: (state, action) => {
    return state.merge({
        status: null,
        error: null,
    })
  },
  [TYPES.REQUEST_SUCCESS]: (state, action) => {
    return state.merge({
        status: "success",
        error: null,
    })
  }
})

然后在reducersindex.js里對(duì)外暴露接口

export request from "./request"

為什么要做這一步呢?因?yàn)槲覀冃枰?b>configureStore.js里利用combineReducers對(duì)所有的reducer進(jìn)行進(jìn)一步的結(jié)合處理:

import { createStore, combineReducers, compose, applyMiddleware } from "redux"
import thunkMiddleware from "redux-thunk"
import createLogger from "redux-logger"
import * as reducers from "./reducers"
import { routerReducer, routerMiddleware } from "react-router-redux"
import { pendingTasksReducer } from "react-redux-spinner"

export default function configureStore(history, initialState) {
  const reducer = combineReducers({
    ...reducers,
    routing: routerReducer,
    pendingTasks: pendingTasksReducer,
  })
  const store = createStore(
    reducer,
    initialState,
    compose(
      applyMiddleware(
        thunkMiddleware,
        routerMiddleware(history) 
      )
    )
  )
  return store
}

接下來(lái)就可以在container里使用了,比如登錄模塊:

/**
 * @desc 登錄模塊 container
 * @createDate 2016-05-16
 * @author Jafeney<[email protected]>
 **/
import React, { Component } from "react"
import { bindActionCreators } from "redux"
import { connect } from "react-redux"
import { replace } from "react-router-redux"
import { login } from "../../actions/user"
import { requestClean } from "../../actions/request"
import CheckUserMixin from "../../mixins/CheckUser"
import PureRenderMixin from "../../mixins/PureRender"
import "../style.less";

class Login extends Component {
    constructor() {
        super()
    }
    shouldComponentUpdate(nextProps, nextState) {
        // 如果已經(jīng)登錄不觸發(fā)深度比較
        if (nextProps.user.getIn(["login", "status"])=="logged") {
            this.toMain()
            return true
        }
        return PureRenderMixin.deepCompare(this, nextProps, nextState)
    }
    // 檢查登錄態(tài)
    componentDidMount() {
        let { user } = this.props;
        if (CheckUserMixin.isLogged(user)) this.toMain()
    }
    // 初始化頁(yè)面
    toMain() {
        this.props.actions.replace("/")
        this.props.actions.requestClean()
    }
    // 執(zhí)行登錄
    login() {
        const userName = this.refs["J_username"].value, password = this.refs["J_password"].value
        if (userName && password) {
            this.props.actions.login({username: userName, password: password})
        }
    }
    // 綁定回車事件
    onEnter(event) {
        var e = event || window.event || arguments.callee.caller.arguments[0];
        if(e && e.keyCode==13) { // enter 鍵
             this.login()
        }
    }
    render() {
        let { user } = this.props
        return (
            
this.onEnter()}>
會(huì)員登錄
this.login()} className="login-btn">登錄 免費(fèi)注冊(cè) | 忘記密碼 ?
{ user.getIn(["login", "error", "message"]) }
) } } // 下面是redux的核心方法 function mapStateToProps(state) { return { user: state.user } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ login, requestClean, replace }, dispatch) } } export default connect(mapStateToProps, mapDispatchToProps)(Login)

注意:通過(guò)以上方式,在組件內(nèi)部actions里掛載的方法就可以通過(guò)this.props取得了。

參考

《webpack 性能優(yōu)化》

《Redux系列01:從一個(gè)簡(jiǎn)單例子了解action、store、reducer》

@歡迎關(guān)注我的 github 和 個(gè)人博客 -Jafeney

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86453.html

相關(guān)文章

  • 單頁(yè)應(yīng)用開發(fā)總結(jié)

    摘要:本文想通過(guò)自己這一年的單頁(yè)應(yīng)用開發(fā)經(jīng)驗(yàn),來(lái)對(duì)的開發(fā)做一個(gè)總結(jié)。但是要知道,現(xiàn)如今頁(yè)面都比較復(fù)雜,一般的單頁(yè)應(yīng)用都需要一個(gè)可靠的數(shù)據(jù)流去處理,否則在日后維護(hù)方面會(huì)難度巨大。 本文想通過(guò)自己這一年的單頁(yè)應(yīng)用開發(fā)經(jīng)驗(yàn),來(lái)對(duì)SPA的開發(fā)做一個(gè)總結(jié)。 頁(yè)面開發(fā)模式 通常我們?cè)陂_發(fā)頁(yè)面時(shí),都會(huì)拿到一份設(shè)計(jì)圖,假設(shè)我們拿到一份這樣的設(shè)計(jì)圖 showImg(https://segmentfault.c...

    zzbo 評(píng)論0 收藏0
  • 專治前端焦慮的學(xué)習(xí)方案

    摘要:不過(guò)今天我希望能夠更進(jìn)一步,不僅僅再抱怨現(xiàn)狀,而是從我個(gè)人的角度來(lái)給出一個(gè)逐步深入學(xué)習(xí)生態(tài)圈的方案。最后,我還是想提到下對(duì)于的好的學(xué)習(xí)方法就是回顧參照各種各樣的代碼庫(kù),學(xué)習(xí)人家的用法與實(shí)踐。 本文翻譯自A-Study-Plan-To-Cure-JavaScript-Fatigue。筆者看到里面的幾張配圖著實(shí)漂亮,順手翻譯了一波。本文從屬于筆者的Web Frontend Introduc...

    codeGoogle 評(píng)論0 收藏0
  • 我為什么從Redux遷移到了Mobx

    摘要:需要注意的是,在中,需要把數(shù)據(jù)聲明為。同時(shí)還提供了運(yùn)行時(shí)的類型安全檢查。在利用了,使異步操作可以在一個(gè)函數(shù)內(nèi)完成并且可以被追蹤。例如在中,數(shù)組并不是一個(gè),而是一個(gè)類的對(duì)象,這是為了能監(jiān)聽到數(shù)據(jù)下標(biāo)的賦值。 Redux是一個(gè)數(shù)據(jù)管理層,被廣泛用于管理復(fù)雜應(yīng)用的數(shù)據(jù)。但是實(shí)際使用中,Redux的表現(xiàn)差強(qiáng)人意,可以說(shuō)是不好用。而同時(shí),社區(qū)也出現(xiàn)了一些數(shù)據(jù)管理的方案,Mobx就是其中之一。 R...

    DevYK 評(píng)論0 收藏0
  • 【譯】Redux 還是 Mobx,讓我來(lái)解決你的困惑!

    摘要:我現(xiàn)在寫的這些是為了解決和這兩個(gè)狀態(tài)管理庫(kù)之間的困惑。這甚至是危險(xiǎn)的,因?yàn)檫@部分人將無(wú)法體驗(yàn)和這些庫(kù)所要解決的問(wèn)題。這肯定是要第一時(shí)間解決的問(wèn)題。函數(shù)式編程是不斷上升的范式,但對(duì)于大部分開發(fā)者來(lái)說(shuō)是新奇的。規(guī)模持續(xù)增長(zhǎng)的應(yīng) 原文地址:Redux or MobX: An attempt to dissolve the Confusion 原文作者:rwieruch 我在去年大量的使用...

    txgcwm 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<