《深入React技術?!饭P記

一、初入React世界

1.2 JSX語法

  • class 屬性修改為className

  • for 屬性修改為 htmFor

  • 展開屬性
    使用ES6 rest/spread 特性提高效率

const data = { name: 'foo', value : 'bar' };
// const component = <Component name={data.name} value={data.value} />;
const component = <Component {...data}>
  • 自定義HTML 屬性
    如果要使用HTML自定義屬性,要使用data- 前綴
<div data-attr="xxx">content</div>
  • HTML 轉義
    dangerouslySetInnerHTML 屬性
<div dangerouslySetInnerHTML={{__html: 'cc &copy:2015'}}></div>

1.3 React 組件

  • props
  1. classPrefix: class前綴。對于組件來說,定義一個統一的class前綴,對樣式與交互分離起了非常重要的作用。
  • 用funtion prop 與父組件通信
  • propTypes
    用于規范 props 的類型與必需的狀態

1.5 React 生命周期

React生命周期分成兩類:

  • 當組件在掛載或卸載時
  • 當組件接收新的數據時,即組件更新時

推薦初始化組件

import React, { Component, PropTypes } from 'react';
class App extends Component {
    // 類型檢查
    static propTypes = {
        // ...
    };
    // 默認類型
    static defaultProps = {
        // ...
    };

    constructor(props) {
        super(props);
        this.state = {
            // ...
        };
    }

    componentWillMount() {
        // ...
    }
    // 在其中使用setState 會更新組件
    componentDidMount() {
        // ...
    }

    render() {
        return <div>This is a demo.</div>
    }
}

componentWillUnmount 常常會執行一些清理方法

  • 數據更新過程
    如果自身的state更新了,那么會依次執行shouldComponentUpdate、componentWillUpdate 、render 和 componentDidUpdate。

如果組件是由父組件更新 props 而更新的,那么在 shouldComponentUpdate 之前會先執行componentWillReceiveProps 方法。

React生命周期整體流程圖

1.6 React與DOM

1.6.1 ReactDOM
其API非常少,只有findDOMNode,unmountComponentAtNode和render

  • findDOMNode
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
  class App extends Component {
     componentDidMount() {
     // this 為當前組件的實例
     const dom = ReactDOM.findDOMNode(this);
   }
   render() {}
} 

findDOMNode只對已經掛載的組件有效
DOM 真正被添加到 HTML 中的生命周期方法是componentDidMount 和 componentDidUpdate 方法

  • render
    把 React 渲染的Virtual DOM 渲染到瀏覽器的 DOM 當中,就要使用 render 方法

React 還提供了一個很少使用的 unmountComponentAtNode 方法來進行
卸載操作。

  • refs
    refs即reference,組件被調用時會新建一個該組件的實例,而refs就會指向這個實例。

二、漫談React

2.1 事件系統
1、事件委派
React沒有把事件直接綁定在真實的節點上,而是綁定在最外層,使用一個統一的事件監聽器,這個事件監聽器上維持了一個映射來保存所有組件內部的事件監聽和處理函數。
React中使用DOM原生事件時,要在組件卸載時手動一處,否則很可能出現內存泄漏的問題。

2.2 表單

3.select組件
單選和多選兩種。在JSX語法中,可以通過設置select標簽的 multiple={true}來實現一個多選下拉列表。

2.2.2 受控組件

每當表單的狀態發生變化時,都會被寫入到組件的state中,這種組件在React中被稱為受控組件(controlled component)。
受控組件更新state的流程:
(1)可以通過在初始 state 中設置表單的默認值
(2)每當表單的值發生變化時,調用onChange事件處理器
(3)事件處理器通過合成事件對象e拿到改變后的狀態,并更新state
(4)setState觸發視圖的重新渲染,完成表單組件值得更新

2.2.3 非受控組件

如果一個表單組件沒有 value props(單選按鈕和復選框對應的是 checked prop)時,就可以稱為非受控組件。相應地,也可以使用 defaultValue 和 defaultChecked prop來表示組件的默認狀態。通常,需要通過為其添加ref prop來訪問渲染后的底層DOM元素。

2.3 樣式處理

2.3.3 CSS Modules
CSS Modules 內部通過ICSS來解決樣式導入和導出兩個問題,分別對應 :import 和 :export 兩個新增的偽類

:import("path/to/dep.css") {
  localAlias: keyFromDep;
  /*...*/
}

:export {
  exporteKey: exportedValue;
  /*...*/
}

啟用 CSS Modules

// webpack.config.js
css?modules&localIdentName=[name]_[local]-[hash:base64:5]

加上 modules 即為啟用,其中 localIdentName 是設置生成樣式的命名規則

使用webpack可以讓全局樣式和CSS Modules的局部樣式和諧共存

module: {
  loaders: [{
    test: /\.jsx?$/,
    loader: 'babel',  
  }, {
    test: /\.scss$/,
    exclude: path.resolve(__dirname, 'src/styles'),
    loader: 'style!css?modules&localIdentName=[name]_[local]!sass?sourceMap=true',
  },{
    test: /\.scsss$/,
    include: path.resolve(__dirname,'src/styles'),
    loader: 'style!css!sass?sourceMap=true',
  }]
}

2.4 組件間通信

  1. 父組件通過props向子組件傳遞需要的信息。
  2. 子組件向父組件通信
  • 利用回調函數
  • 利用自定義事件機制
  1. 跨級組件通信
    React中,我們可以使用 context 來實現跨級父子組件間的通信
// listItem組件
class ListItem extends Component {
  static contextTypes = {
    color: PropTypes.string,
  };
  render() {
    const { value } = this.props;

    return (
      <li style={{background: this.context.color}}>{value}</li>
    )
  }
 }


// List組件
class List extends Component {
  static childContextTypes = {
    color: PropTypes.string,
  };
  getChildContext() {
    return {
        color: 'red'
    }
  }
}

父組件中定義了 ChildContext,這樣從這一層開始的子組件都可以拿到定義的context。

2.5.2 高階組件

實現高階組件的方法有如下兩種

  • 屬性代理(props proxy)。高階組件通過被包裹的React組件來操作 props
  • 反向繼承(inheritance inversion)。高階組件繼承于被包裹的React組件
  1. 屬性代理
import React, { Component } from 'react';

const MyContainer = (WrappedComponent) => {
  class extends Component {
    render() {
      return <WrappedComponent {...this.props}>
    }
  }
}

當然我們也可以用 decorator 來轉換

import React, { Component } from 'react';
@MyContainer
class MyComponent extends Component {
  render();
}

export default MyComponent;

簡單地替換成作用在類上的decorator,即接受需要裝飾的類為參數。

  • 控制 props
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
  class extends Component {
    render() {
      const newProps = {
         text: newText,
      };
      return <WrappedComponent {...this.props} {...newProps}>
    }
  }
}
  • 通過 refs 使用引用
    高階組價中,我們可以接受 refs 使用 WrappedComponent 的引用。
import React, { Component } from 'react';
const MyContainer = (WrappedComponent) => {
  class extends Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method();
    }
    render() {
      const props = Object.assign({}, this.props, {
          ref: this.proc.bind(this),
      });
      return <WrappedComponent {...props}>
    }
  }
}
  • 抽象 state
    抽象一個input組件
import React, { Componet } from 'react';
const MyContainer = (WrappedComponent) => {
  class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        name:'',
      }

      this.onNameChange = this.onNameChange.bind(this);
    }
  }
 onNameChange(event) {
      this.setState({
          name: event.target.value,
      });
    }

    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange,
        }
      }
      return <WrappedComponent {...this.props} {...newProps} />
    }
}
  • 使用其他元素包裹 WrappedComponent
import React,{ Component } from 'react';
const MyContainer = (WrappedComponent) => {
  class extends Component {
      render() {
        return (
            <div style={{display: 'block'}}>
              <WrappedComponent {...this.props}>
            </div>
         )
      }
   }
}


// 受控input組件使用
@MyContainer
class MyComponent extends Component {
  render() {
    return <input name="name" {...this.props.name}>
  }
}

高階組件和mixin的不同

mixin與高階組件的區別.png
  1. 反向繼承
  • 渲染劫持
const MyContainer = (WrappedComponent) => {
  class extends WrappedComponent {
    render() {
      if(this.props.loggedIn) {
        return super.render();
      } else {
        return null;
      }
    }
  }
}

// 實例二:對render結果進行修改
const MyContainer = (WrappedComponent) => {
   class extends WrappedComponent {
    render() {
       const elementsTree = super.render();
       let newProps = {};

      if(elementsTree && elementsTree.type === 'input'){
        newProps = {value: 'May the force be with you'};
      }
      const props = Object.assign({}, elementsTree.props, newProps);
      const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.childre);
      return newElementsTree;
     }
   }
}
  • 控制state
const MyContainer = (WrappedComponent) => {
  class extends WrappedComponent {
    render() {
      return (
        <div>
            <h2>HOC Debugger Component</h2>
             <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
             <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
             {super.render()} 
        </div>
      )
    }
  }
}
  1. 組件命名
  2. 組件參數
import React, { Component } from 'react';
function HOCFactory(...params) {
  return function HOCFactory(WrappedComponent) {
    return class HOC extends Component {
      render() {
        return <WrappedComponent {...this.props}>
      }
    }
   }
}

// 使用
HOCFactoryFactory(params)(WrappedComponent);
// 或者
@HOCFactoryFactory(params);
class WrappedComponent extends React.Component{}

2.6 組件性能優化

  1. 純函數
  • 給定相同的輸入,它總能返回相同的輸出
  • 過程沒有副作用
  • 沒有額外的狀態依賴
  1. PureRender
    為重新實現了 shouldComponentUpdate 生命周期方法,讓當前傳入的 props
    和 state 與之前的作淺比較,如果返回 false,那么組件就不會執行 render 方法。

  2. react-addons-perf
    量化所做的性能優化效果
    Perf.start()
    Perf.stop()

2.7 動畫

TransitionGroup 能幫助我們快捷地識別出增加或刪除的組件。

React Transition設計了以生命周期函數的方式來實現,即讓子組件的每一個實例都實現相應地生命周期函數。當React Transition識別到某個子組件增或刪時,則調用它相應地生命周期函數。我們可以再生命周期函數中實現動畫邏輯。
如果每一個子組件的動效相同,那么每一個子組件可以共同用一個生命周期函數。因此React Transition 提供了 childFactory 配置,讓用戶自定義一個封裝子組件的工廠方法,為子組件加上相應地生命周期函數。
React Transition提供的生命周期

  • componentWillAppear
  • componentDidAppear
  • componentWillEnter
  • componentDidEnter
  • componentWillLeave
  • componentDidLeave

componentWillxxx 只要在 componentWillReceiveProps中對this.props.childrennextProps.children做一個比較就可以了。componentDidxxx可以在componentWillxxx提供一個回調函數,用來執行componentDidxxx

React CSS Transition 為子組件的每個生命周期加了不同的className,這樣用戶可以很方便地根據 className 地變化來實現動畫

<ReactCSSTransitionGroup
  transitionName="example"
  transitionEnterTimeout={400}
>
{items}
</ReactCSSTransitionGroup>

對應地css代碼

.example-enter {
  transform: scaleY(0);
  &.example-enter-active {
    transform: scaleY(1);
    transition: transform .4s ease;
  }
}

使用react-motion實現一個spring開關

import React, {Component} from ''react;

class Switch extends Component {
  constructor(props) {
    super(props);

    this.handleClick = this.handleClick.bind(this);

    this.state = {
      open: false,
    }
  }
  handleClick() {
     this.setState({
      open: !this.state.open
    })
  }
  render() {
    return (
      <Motion style={{x: spring(this.state.open ? 400 : 0)}}>
          {({x}) =>
           <div className="demo">
             <div
               className="demo-block"
               onClick={this.handleClick}
               style={{
                 transform: `translate3d(${x}px, 0, 0)`,
          }}
 />
 </div>
 } 
      </Motion>
    )
  }
}

深入Redux 應用框架

5.1 Redux簡介

5.1.2Redux三大原則

  1. 單一數據源
  2. 狀態是只讀地
  3. 狀態修改均由純函數完成

5.1.3 Redux 核心API

Redux核心是一個store,這個store是由createStore(reducers[,initialState])方法生成

通過createStore方法創建的store是一個對象,包含4個方法

  • getState(): 獲取store中當前的狀態
  • dispatch(action):分發一個action,并返回這個action,這是唯一能改變store中數據的方式
  • subscribe(listener): 注冊一個監聽者,它在store發生改變時被調用
  • replaceReducer(nextReducer): 更新當前store里的reducer,一般只會在開發模式中調用該方法。

5.1.4 與React 綁定

需要使用react-redux進行react和redux的綁定,其提供了一個組件和API幫助Redux和React進行綁定,一個是 React組件<Provider />,一個是 connect(),<Provider />接受一個 store 作為props,它是整個Redux應用的頂層組件,而connect()提供了在整個React應用的任意組件中獲取store中數據的功能。

5.2 Redux middleware

Redux 提供了 applyMiddleware 方法來加載 middleware,其源碼如下

import compose from './compose';

export default function applyMiddleware(...middlewares) {
  return (next) => (reducer, initialState) => {
    // 獲取得到原始的store
    let store = next(reducer, initialState);
    let dispatch = store.dispatch;
    // 賦值一個空數組,用來存儲后新的dispatch分裂函數
    let chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
     };

    chain = middlewares.map(middleware => middleware(middlewareAPI));
    // 將分裂的函數組合每次都會執行,即每次都執行這些中間件函數
    dispatch = compose(...chain)(store.dispatch);

    return {
      ...store,
      dispath
    }
  }
}

middleware運行原理

  1. 函數式編程思想設計
    通過函數式編程中的 currying。currying 的middleware結構的好處有以下兩點
  • 易串聯:不斷currying形成的middleware可以累積參數,再配合組合方式,很容易形成 pipeline來處理數據流
  • 共享store:applyMiddleware執行過程中,store還是舊的,applyMiddleware完成后,所有的middleware內部拿到的sotore都是最新且相同的
  1. 給middleware分發store
    let newStore = applyMiddleware(mid1, mid2, mid3)(createStore)(reducer, null);

  2. 組合串聯middleware
    dispatch = compose(...chain)(store.dispatch)
    Redux中compose的實現

function compose(...funs) {
  return arg => funs.reduceRight((composed, f) => f((composed), arg))
}

compose(...funcs) 返回的是一個匿名函數,其中 funcs 就是 chain 數組。當調用 reduceRight
時,依次從 funcs 數組的右端取一個函數 fx 拿來執行,fx 的參數 composed 就是前一次 fx+1 執
行的結果,而第一次執行的 fn(n 代表 chain 的長度)的參數 arg 就是 store.dispatch。

  1. 在 middleware 中調用 dispatch 會發生什么
Redux middleware流程圖.png

如果這個middleware粗暴的調用 store.dispatch(acton),就會形成無線循環了。
這里我們就用到了Redux Thunk。
Redux Thunk 會判斷 action 是否是函數。如果是,則執行 action,否則繼續傳遞 action 到下一個 middleware。

const tuhun = store => next => action => {
  typeof action === 'function' ?
    action(store.dispatch, store.getState) :
    next(action)
}

5.3 Redux 異步流

5.3.1 使用 middleware 簡化異步請求

  1. redux-thunk
    我們再來看看 redux-thunk 的源代碼:
function createThunkMiddleware(extraArgument) {
 return ({ dispatch, getState }) => next => action => {
   if (typeof action === 'function') {
     return action(dispatch, getState, extraArgument);
   }
   return next(action);
 };

模擬請求天氣的異步請求,action的寫法

function getWeather(url, params) {
   return (dispatch, action) {
    fetch(url, params)
      .then(result => {
        dispatch({
          type: 'GET_WEATHER_SUCCESS',
          payload: result,
        })
      })
      .catch(err => {
        dispatch({
          type: 'GET_WEATHER_ERROR',
          payload: err,
        })
      })
  }
}
  1. redux-promise

import { isFSA } from 'flux-standard-action';
function isPromise(val) {
 return val && typeof val.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
 return next => action => {
 if (!isFSA(action)) {
 return isPromise(action)
 ? action.then(dispatch)
 : next(action);
 }
 return isPromise(action.payload)
 ? action.payload.then(
 result => dispatch({ ...action, payload: result }),
 error => {
 dispatch({ ...action, payload: error, error: true });
 return Promise.reject(error);
 }
 )
 : next(action);
 };
} 

我們利用 ES7 的 async 和 await 語法,可以簡化上述異步過程:

const fetchData = (url, params) => fetch(url, params);
async function getWeather(url, params) {
 const result = await fetchData(url, params);
 if (result.error) { 
220 第 5 章 深入 Redux 應用架構
 return {
 type: 'GET_WEATHER_ERROR',
 error: result.error,
 };
 }
 return {
 type: 'GET_WEATHER_SUCCESS',
 payload: result,
 };
} 
  1. redux-saga
    在 Redux 社區,還有一個處理異步流的后起之秀,名為 redux-saga。它與上述方法最直觀的
    不同就是用 generator 替代了 promise,我們通過 Babel 可以很方便地支持 generator.

Redux 與 路由

我們可以通過 <Router> 、<Route> 這兩個標簽以及一系列屬性
定義整個 React 應用的路由方案。

前端開發熱加載,安裝 webpack-dev-server

npm install -D webpack-dev-server

./node_modules/.bin/webpack-dev-server --hot --inline --content-base

在 mapStateToProps 中,我們從整棵 Redux 狀態樹中選取了 state.home.list 分支作為當前
組件的 props,并將其命名為 list。這樣,在 Home 組件中,就可以使用 this.props.list 來獲取
到所有 PreviewListRedux 中定義的狀態。
而在 mapDispatchToProps 中,我們從前面提到的 HomeRedux.js 中引入了 listActions,并使
用 Redux 提供的工具函數將 listActions 中的每一個 action creator(目前只有一個)與 dispatch 進
行綁定,最終我們可以在 Home 組件中使用 this.props.listActions 來獲取到綁定之后的 action
creator。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 做React需要會什么? react的功能其實很單一,主要負責渲染的功能,現有的框架,比如angular是一個大而...
    蒼都閱讀 14,818評論 1 139
  • 學習必備要點: 首先弄明白,Redux在使用React開發應用時,起到什么作用——狀態集中管理 弄清楚Redux是...
    賀賀v5閱讀 8,953評論 10 58
  • 有槽先吐 花了幾天時間,大致讀完了《深入React技術棧》,簡單總結的話,不及預期。 作者成書前,在知乎開設pur...
    ronniegong閱讀 5,013評論 1 8
  • 前言 本文 有配套視頻,可以酌情觀看。 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯系我討論。 文中所有內...
    珍此良辰閱讀 11,948評論 23 111
  • 一個吃過午飯犯困的午后,加上感冒藥的作用,沒精神。 昨天得知要去外地培訓的事情,打破了我的計劃。設想了很多的后果,...
    熊貓ii閱讀 336評論 0 0