ホームページ  >  記事  >  ウェブフロントエンド  >  redux テクノロジースタックを深く掘り下げる

redux テクノロジースタックを深く掘り下げる

高洛峰
高洛峰オリジナル
2017-02-18 14:24:391440ブラウズ

redux入門

この記事は、誰もがreactとfluxアーキテクチャについてある程度の知識を持ち、reduxを使用または理解していることを前提としているため、最も基本的なことから始めるのではなく、reduxについて直接要約します。 redux を使用したことがない場合は、最初にここを読むことをお勧めします

redux を理解するには、まず redux の設計原則をいくつか要約する必要があります:

  • 単一データ ソース

Redux は単一オブジェクトの大きなツリー構造のみを使用しますアプリケーション全体の状態、つまりアプリケーション全体で使用されるデータを保存する目的をストアと呼びます。ストアには保存されたデータに加えて、アプリケーション全体の状態(後で紹介するルータの状態も含む)も保存できるため、ストアを介して即時保存機能(スナップショットの作成)を実装することが可能になります。 ) さらに、この設計はサーバー側のレンダリングの可能性も提供します。

  • ステータスは読み取り専用です

これは、flux の設計コンセプトに沿ったものであり、コンポーネント内でストアのステータスを変更することはできません (実際には、redux はリデューサーに基づいてストアを生成します)。アクションはディスパッチによってのみトリガーされます。ここでは、アプリケーションの状態を直接変更するのではなく、まったく新しい状態を返します。

  • 状態の変更はすべて純粋な関数で構成されています

Redux のリデューサーのプロトタイプは次のようになります。以前の状態 + アクション = 新しい状態の式として考えることができます:

(previousState, action) => newState

各リデューサー。これは純粋な関数であり、副作用がないことを意味します。この設計の利点は、状態を変更するためにレデューサーを使用するのが簡単であるだけでなく、redux は各戻り状態を保存できることです。タイムトラベルを簡単に生成し、アクションによって引き起こされた各変化の結果を追跡します。

react で redux を使用する場合は、react-redux と redux の両方が必要です。

reduxアーキテクチャとソースコード分析

この部分は主に私自身の理解について話します。少し抽象的であったり、完全に正しくないかもしれないので、直接読み飛ばしていただいても大丈夫です。

createStore

react のコア関数はすべて createStore でカバーされており、createStore メソッド自体は、reducer、initialState、および Enhancer の 3 つのパラメーターの受け渡しをサポートしています。強化されたパッケージ化機能として。これはあまり使用されません。

この関数は内部で currentState を維持し、この currentState は getState 関数 (組み込み) を通じて返すことができます。さらに、実際にはパブリッシュ/サブスクライブ モードを実装し、store.subscribe を通じてイベントをサブスクライブします。暗黙的にこれを行うのを手伝ってください。これは、ディスパッチがあるときにすべてのリスナーをトリガーし、状態ツリー全体を更新することです。さらに、一連の検証後に組み込みのディスパッチ関数がリデューサーをトリガーし、その後状態が変更され、リスナーが順番に呼び出されて状態ツリー全体の更新が完了します。

middleWare

redux を使用したことのある友人は、redux-thunk などのミドルウェアに精通しているわけではありません。実際、Redux は、ミドルウェアの優れたサポートも備えています。メカニズムはある程度似ています。アクションは各ミドルウェアを順番に通過し、次に次のミドルウェアに渡されます。また、各ミドルウェアは、最終処理関数がリデューサーに引き渡されるまで、アクションの中断や変更などの他の操作も実行できます。

redux の applyMiddleware 関数は非常に洗練されています:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
      //注意这里的dispatch并不是一开始的store.dispatch,实际上是变化了的
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

コアは、dispatch = compose(...chain)(store.dispatch) です。compose のソース コード:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

Call。前の関数の実行結果を次の関数に渡します。

実際、ミドルウェアを作成するプロセスは非常に簡単です。たとえば、redux-trunk は実際には次の要素で構成されています:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

redux と routing

もちろん、最初に React ツール セットの React-router を宣言します。は必ずしも必要ではありません redux と併用することもできますが、redux には別の reverse-router-redux があり、react-router と redux で併用でき、その効果は非常に優れています。

このパートではreact-routerの使い方は紹介しませんので、react-routerの使い方については中国語のドキュメントを参照してください。

react-router

  • の機能により、開発者は JSX タグを通じてルートを宣言できるため、ルートを非常に書きやすくなり、宣言型ルーティングの表現能力が比較的強力になります。

  • ネストされたルーティングとルートマッチング: 指定されたパスでパラメーターを渡すことができます:

<Route path="/mail/:mailId" component={Mail} />

さらに、パラメーターがオプションの場合は、括弧で囲むことができます (:オプションのパラメーター)。

  • 複数のルート切り替え方法をサポート: 現在のルート切り替え方法は、hashchange と PushState を使用するだけであることがわかっています。前者はブラウザーとの互換性が優れていますが、実際の URL とは異なり、後者は URL を提供します。エレガントな URL エクスペリエンスですが、サーバーはパスを更新するという問題を解決する必要があります (サーバーは自動的にホームページにリダイレクトする必要があります)。

为什么需要react-router-redux

简单的说,react-router-redux让我们可以把路由也当作状态的一部分,并且可以使用redux的方式改变路由:直接调用dispatch:this.props.push(“/detail/”);,这样把路由也当作一个全局状态,路由状态也是应用状态的一部分,这样可能更有利于前端状态管理。

react-router-redux是需要配合react-router来使用的,并不能单独使用,在原本的项目中添加上react-router-redux也不复杂:

import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import { hashHistory } from 'react-router';

import ThunkMiddleware from 'redux-thunk';
import rootReducer from './reducers';
import DevTools from './DevTools';

const finalCreateStore = compose(
  applyMiddleware(ThunkMiddleware,routerMiddleware(hashHistory)),
  DevTools.instrument()
)(createStore);

console.log("rootReducer",rootReducer);

const reducer = combineReducers({
  rootReducer,
  routing: routerReducer,
});

export default function configureStore(initialState) {
  const store = finalCreateStore(reducer, initialState);
  return store;
}

另外,上文提到的demoreact-router-redux-demo用了react-router和react-router-redux,当然也用到了redux的一些别的比较好的工作,比如redux-devtools,有兴趣的朋友可以点击这里

redux 与组件

这一部分讲述的是一种组件书写规范,并不是一些库或者架构,这些规范有利于我们在复杂的项目中组织页面,不至于混乱。

从布局的角度看,redux强调了三种不同的布局组件,Layouts,Views,Components:

  • Layouts: 指的是页面布局组件,描述了页面的基本结构,可以是无状态函数,一般就直接设置在最外层router的component参数中,并不承担和redux直接交互的功能。比如我项目中的Layouts组件:

const Frame = (props) =>
       <p className="frame">
           <p className="header">
               <Nav />
           </p>
           <p className="container">
               {props.children}
           </p>
       </p>;
  • Views组件,我认为这个组件是Components的高阶组件或者Components group,这一层是可以和redux进行交互并且处理数据的,我们可以将一个整体性功能的组件组放在一个Views下面(注:由于我给出的demo十分简单,因此Views层和Components层分的不是那么开)

  • Components组件,这是末级渲染组件,一般来说,这一层级的组件的数据通过props传入,不直接和redux单向数据流产生交互,可以是木偶般的无状态组件,或者是包含自身少量交互和状态的组件,这一层级的组件可以被大量复用。

总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。

redux 与表单

redux的单向数据流相对于双向数据绑定,在处理表单等问题上的确有点力不从心,但是幸运的是已经开源了有几个比较不错的插件:

  • redux-form-utils,好吧,这个插件的star数目非常少,但是他比较简单,源代码也比较短,只有200多行,所以这是一个值得我们看源码学习的插件(它的源码结构也非常简单,就是先定一个一个高阶组件,这个高阶组件可以给我们自己定义的表单组件传入新的props,定制组件,后一部分就是定义了一些action和reducer,负责在内容变化的时候通知改变状态树),但是缺憾就是这个插件没有对表单验证做工作,所以如果我们需要表单验证,还是需要自己做一些工作的。

    • 另外还有一地方,这个插件源代码写法中用到了::这种ES6的语法,这其实是一种在es6中class内部,使用babel-preset-stage-0即可使用的语法糖:::this.[functionName] 等价于 this.[functionName].bind(this, args?)  

  • redux-form,这个插件功能复杂,代码完善,体量也非常庞大,可以参考文档进行使用,但是读懂源代码就是比较麻烦的事情了。不过这个插件需要在redux的应用的state下挂载一个节点,这个节点是不需要开发者自己来操控的,他唯一需要做的事情就是写一个submit函数即可。我在自己的demo中也把一个例子稍加改动搬了过来,感觉用起来比较舒服。

redux 性能优化

想要做到redux性能优化,我们首先就要知道redux的性能可能会在哪些地方受到影响,否则没有目标是没有办法性能优化的。

因为我也不是使用redux的老手,所以也并不能覆盖所有性能优化的点,我总结两点:

  • 有的时候,我们需要的数据格式会自带冗余,可以抽取出一些公共的部分从而缩减大小,比如我们需要的数据格式可能是这样的:

[
    {
        name:"Nike",
        title:"国家一级运动员","国家一级裁判员"
    }
    {
        name:"Jenny",
        title:"国家一级裁判员"
    }
    {
        name:"Mark",
        title:"国家一级运动员"
    }
]

这个时候实际上我们可以优化成这样:

[
    {
    "国家一级运动员":"Nike","Mark"
    "国家一级裁判员":"Jenny","Nike"
    }
]

这个时候,我们可以直接把后者当作store的格式,而我们用reselect这个库再转变成我们所要的格式,关于reselect怎么用上述链接有更详细的例子,在这里我就不过多介绍了。

  • 事实上,对于redux来说,每当store发生改变的时候,所有的connect都会重新计算,在一个大型应用中,浪费的时间可想而知,为了减少性能浪费,我们可以对connect中的selector做缓存。

上記の再選択ライブラリには独自のキャッシュ機能があり、パラメータを比較することでキャッシュを使用するかどうかを決定できます。ここでは純粋な関数機能が使用されます。

reselect のキャッシュ機能はユーザーがカスタマイズできます


Redux テクノロジースタック関連の記事については、PHP 中国語 Web サイトに注目してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。