React 入門實踐

歡迎移步個人博客閱讀:《React 入門實踐》css

在寫這篇文章以前,我已經接觸 React 有大半年了。在初步學習 React 以後就正式應用到項目中,當時就想把本身的一些想法寫出來分享一下,無奈不太會寫文章,再則時間不是很充裕,因此也就擱下了。html

本篇文章比較基礎,沒有深刻的分析,大神們輕看。廢話就很少說了,那麼讓咱們來進入正題。node

簡介

首先想要介紹的是 React,看到這篇文章的朋友想必都有一些關於 React 的瞭解了,但對於剛接觸的新人而言,在這就要簡要地介紹一下了。而後就是關於使用 React 構建一個簡單單頁應用(下文用 SPA 代替,Single Page Application)的一些介紹和講解。react

關於 React

React 起源於 Facebook 的內部項目,由於該公司對市場上全部 JavaScript MVC 框架,都不滿意,就決定本身寫一套,用來架設Instagram 的網站。作出來之後,發現這套東西很好用,就在2013年5月開源了。(更多相關介紹請看這webpack

特色:git

  • 僅僅只是 UIgithub

  • 虛擬 DOM:最大限度減小與 DOM 的交互(相似於使用 jQuery 操做 DOM)web

  • 單向數據流:很大程度減小了重複代碼的使用express

組件化:npm

  • 可組合(Composeable):一個組件易於和其它組件一塊兒使用,或者嵌套在另外一個組件內部。若是一個組件內部建立了另外一個組件,那麼說父組件擁有(own)它建立的子組件,經過這個特性,一個複雜的UI能夠拆分紅多個簡單的UI組件

  • 可重用(Reusable):每一個組件都是具備獨立功能的,它能夠被使用在多個UI場景

  • 可維護(Maintainable):每一個小的組件僅僅包含自身的邏輯,更容易被理解和維護

生命週期:

  • Mounting:已插入真實 DOM

  • Updating:正在被從新渲染

  • Unmounting:已移出真實 DOM

React 爲每一個狀態都提供了兩種處理函數,will 函數在進入狀態以前調用,did 函數在進入狀態以後調用,三種狀態共計五種處理函數。

  • componentWillMount()

  • componentDidMount()

  • componentWillUpdate(object nextProps, object nextState)

  • componentDidUpdate(object prevProps, object prevState)

  • componentWillUnmount()

此外,React 還提供兩種特殊狀態的處理函數。

  • componentWillReceiveProps(object nextProps):已加載組件收到新的參數時調用

  • shouldComponentUpdate(object nextProps, object nextState):組件判斷是否從新渲染時調用

正題

那麼進入正題,花了點時間去寫一個簡單的 SPA,也算是一個比較完整 React 骨架,但不包括測試(測試的教程能夠看這個),相關源碼能夠查看 react-start-kit

接下來看看咱們這個項目的構建須要用到些什麼:

  • react

  • redux

  • webpack

  • react-router

  • ant design

  • babel
    ...

還有一些沒有列舉出來,具體能夠看倉庫源碼的 package.json。其中的詳細介紹會在文尾列出一些我所看過的文章或是官方介紹。

配置項

Webpack

說到 React 項目的構建就不得不提 Webpack 這個神器。構建工具備不少,例如 Grunt,Gulp,Brunch 等,相比這些構建工具,Webpack 感受就是和 React 不謀而合,尤爲是 react-hot-loader 這樣的神器(熱加載),讓 Webpack 成爲最主流的 React 構建工具。

關於 Webpack 的特性以及介紹這裏就不贅述了,咱們能夠從下圖看出 Webpack 的做用:

接着咱們從項目代碼中來看 Webpack。

entry: {
  app: [__dirname + '/src/index'],
},
output: {
  path: __dirname + '/_dist',
  filename: '[name]_[hash].js',
}

這部分主要是指定入口和出口文件。entry 做爲項目的入口文件;output 做爲文件編譯後的出口,其中 path 表明輸出的路徑,filename 表明文件名稱,而 [name]_[hash] 保證了瀏覽器不會存在緩存(即修改文件後效果不生效)。

module: {
  loaders: [{
    test: /\.js$/,
    loaders: ['babel'],
    exclude: /node_modules/,
  }, {
    test: /\.css$/,
    loaders: ['style', 'css'],
    include: /components/,
  }, {
    test: /\.(jpe?g|png|gif|svg|ico)/i,
    loader: 'file',
  }, {
    test: /\.(ttf|eot|svg|woff|woff2)/,
    loader: 'file',
  }, {
    test: /\.(pdf)/,
    loader: 'file',
  }, {
    test: /\.(swf|xap)/,
    loader: 'file',
  }],
}

而這部分會幫助咱們去處理不一樣類型的文件,其中 test 就是文件的後綴,loaders 是「轉譯器」,include 是指定文件的目錄,exclude 是排除某個目錄。咱們能夠看出,全部的 .js 文件都會經過 babel 去轉譯,也就是咱們在項目中使用 ES6+ 語法會經過 babel 轉譯成瀏覽器能夠識別的 ES5 代碼。

最後配置好的 config 是這樣的:

var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    app: [__dirname + '/src/index'],
  },
  output: {
    path: __dirname + '/_dist',
    filename: '[name]_[hash].js',
  },
  resolve: {
    root: [
      __dirname + '/src',
      __dirname + '/node_modules',
      __dirname,
    ],
    extensions: ['', '.js'],
  },
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: ['babel'],
      exclude: /node_modules/,
    }, {
      test: /\.css$/,
      loaders: ['style', 'css'],
      include: /components/,
    }, {
      test: /\.(jpe?g|png|gif|svg|ico)/i,
      loader: 'file',
    }, {
      test: /\.(ttf|eot|svg|woff|woff2)/,
      loader: 'file',
    }, {
      test: /\.(pdf)/,
      loader: 'file',
    }, {
      test: /\.(swf|xap)/,
      loader: 'file',
    }],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: __dirname + '/src/index.html',
      favicon: __dirname + '/src/favicon.ico',
      inject: false,
    }),
  ],
};

Express 服務器啓動

Node.js web 應用開發框架 Express 做爲項目的 web 服務器,有 Node.js 開發經驗的同窗應該挺熟悉的了,這裏也很少作贅述。

最終的啓動代碼是這樣的:

var express = require('express');
var webpack = require('webpack');
var webpackConfig = require('./webpack.development');

var app = express();
var compiler = webpack(webpackConfig);

app.use(require('webpack-dev-middleware')(compiler, {
  stats: {
    colors: true,
  },
}));

app.use(require('webpack-hot-middleware')(compiler)); //熱加載

app.listen(process.env.PORT, function(err) { //在沒有端口的狀況下,會自動給出一個隨機端口
  if (err) {
    console.log(err);
  }
});

爲了方便咱們的訪問,項目使用了 minihost 進行啓動,方便快捷。值得一提的是,使用 h -- npm start 命令啓動時,訪問的是項目文件夾的名稱做爲連接,例如項目叫 myproject,那麼此時能夠訪問 myproject.t.t

Redux

對於複雜的 SPA,狀態(state)管理很是重要。state 可能包括:服務端的響應數據、本地對響應數據的緩存、本地建立的數據(好比,表單數據)以及一些 UI 的狀態信息(好比,路由、選中的 tab、是否顯示下拉列表、頁碼控制等等)。若是 state 變化不可預測,就會難於調試(state 不易重現,很難復現一些 bug)和不易於擴展(好比,優化更新渲染、服務端渲染、路由切換時獲取數據等等)。

state 爲單一對象,使得 Redux 只須要維護一棵狀態樹,服務端很容易初始化狀態,易於服務器渲染。state 只能經過 dispatch(action) 來觸發更新,更新邏輯由 reducer 來執行。

在使用 Redux 後,state 就變得很容易維護,並且數據流很是清晰,容易解決遇到的 BUG。

咱們能夠看下圖來簡要地理解 Redux:

咱們能夠在項目中看到的結構是:

├─store
├─actions
├─reducers
├─constants
├─helpers
├─components
├─app.js
├─favicon.ico
├─index.html
├─index.js
└─routes.js

最終咱們的 store 是:

import {createStore, applyMiddleware, combineReducers, compose} from 'redux';
import thunk from 'redux-thunk';
import {reduxReactRouter} from 'redux-router';
import createHistory from 'history/lib/createHashHistory';

import routes from '../routes';
import * as reducers from '../reducers';

let middlewares = [thunk];

if (process.env.NODE_ENV === 'development') { //在開發環境下能夠看到 state 的 log
  const logger = require('redux-logger');
  middlewares = [...middlewares, logger];
}

const finalCreateStore = compose( //組合多個函數
  applyMiddleware(...middlewares),
  reduxReactRouter({routes, createHistory}),
)(createStore); //建立 store 來管理全部的 state

export default function configureStore(initialState) {
  const reducer = combineReducers(reducers);  //把一個由多個不一樣 reducer 函數做爲 value 的 object,合併成一個最終的 reducer 函數
  const store = finalCreateStore(reducer, initialState);

  if (process.env.NODE_ENV === 'development' && module.hot) { //開發環境下的熱加載
    module.hot.accept('../reducers', () => {
      const nextReducers = require('../reducers');
      const nextReducer = combineReducers(nextReducers);
      store.replaceReducer(nextReducer);
    });
  }

  return store;
}

獲取 state 須要在組件中調用 connect 函數,能夠自行定義須要獲取的 state。(這用於區分展現型和容器型組件)

...
@connect(
  state => ({
    data: state.data
  })
)
export default class ComponentOne extends Component {
  ...
}

注意connect 必須緊跟 component 的定義,否則會報錯。

Router

爲項目添加路由系統,使用了 react-router 來管理路由。在開發項目的時候,比較推薦的作法是使用路由去跳轉頁面,而且建立 store 的同時咱們就把 router 加入其中,而後咱們根據路由的變化去更新視圖。

咱們能夠看看路由的源碼:

import React from 'react';
import Route from 'react-router/lib/Route'; //import {Route} from 'react-router';
import Base from 'components/base/Base';
import Home from 'components/home/Home';

export default (
  <Route component={Base}>
    <Route path="/" component={Home} />
    <Route path="/home" component={Home} />
  </Route>
);

path 是跳轉路徑,component 是與路徑相匹配的組件。

Ant Design

由螞蟻金服技術部出品的一個 UI 設計語言,也是項目中所用到的 UI 組件庫。

特性:

  • Designed as Ant Design,提煉和服務企業級中後臺產品的交互語言和視覺風格

  • React Component 上精心封裝的高質量 UI 庫

  • 基於 npm + webpack + babel 的工做流,支持 ES2015

選擇理由:

  • 有很好的技術支持

  • 簡潔的樣式

  • 基本涵蓋經常使用組件
    ...

簡單的 Component

組件做爲 React 渲染的一個基本組成,咱們一般把它們分爲兩類,容器型展現型。相較於容器型展現型是經過容器型傳遞 props 來獲取數據,而容器型能夠直接從 store 中獲取,處理並傳遞給下級組件。

在實際應用中會發現,定義一個容器型組件負責處理數據,而後分發給下級展現型組件,當須要更新數據時,那麼容器型組件發生變化會引發下級展現型組件的變化,這樣就對咱們業務上形成了必定的困擾(在不須要更新的部分組件上也發生了更新)。所以,咱們選擇在須要獲取數據的組件中使用 connect,這樣則會方便不少(感受有些違反規則)。

在項目中咱們會這麼定義組件:

import React, {Component} from 'react';
import {connect} from 'react-redux';
import Presentational from 'components/common/Presentational';

@connect(
  state => ({
    data: state.data
  })
)
export default class Container extends Component {

  render() {
    const {data} = this.props;

    return (
      <Presentational data={data} />
    )
  }
}

上面是能夠從 store 獲取數據的組件,並嵌套另外一個組件,將數據傳遞給它。

import React, {Component, PropTypes} from 'react';

export default class Presentational extends Component {

  static propTypes = {
    data: PropTypes.string,
  }

  render() {
    const {data} = this.props;

    return (
      <div>
        {data}
      </div>
    )
  }
}

獲取上一個組件傳遞過來的數據,並展現出來。

總結

這是一篇科普文(哈哈~囧),並無深刻去分析各項技術的具體內容,但願能幫助剛入手 React 的新手們。實踐項目的源碼能夠在 react-start-kit 看到,你能夠下載這個項目進行本身的一些探索和開發。還在努力探索中,文中有措辭不當或是疏漏,歡迎提出意見和建議。

參考

react 官網
Babel 官網
redux 介紹
redux 中文文檔
Ant design 官網
React 入門實例教程
react-router 中文文檔
Webpack 傻瓜式指南(一)
CSS Modules 詳解及 React 中實踐
一看就懂的 ReactJs 入門教程(精華版)
深刻淺出React(二):React開發神器Webpack

相關文章
相關標籤/搜索