メインコンテンツへ移動

dva

無料2017-10-22#JS#dvajs#dvajs原理#dvajs入门#dvajs教程#dvajs tutorial

業界の react&redux ベストプラクティスに基づくビジネスフレームワーク

一.目標定位

何を解決したいのか?どのようにするつもりか?

簡単に言えば:dva業界の react&redux ベストプラクティスに基づくビジネスフレームワークを提供し、裸の redux 全家桶をフロントエンドデータ層として使用することによる様々な問題を解決したいと考えています

  • 編集コストが高く、reducer、saga、action の間を行き来する必要がある
  • ビジネスモデル(またはドメインモデル)の組織化が不便。例えば userlist を書いた後、productlist を書く場合、多くのファイルをコピーする必要がある。
  • saga の記述が複雑すぎる。action を 1 つ監視するごとに fork -> watcher -> worker のフローを辿る必要がある
  • redux entry の記述が面倒。store の作成、ミドルウェア設定、ルートの初期化、Provider の store のバインド、saga の初期化を完了する必要がある

例えば:

+ src
  + sagas
    - user.js
  + reducers
    - user.js
  + actions
    - user.js
  + service
    - user.js

二.コア実装

どのようにしたのか?

依存関係

dva
  react
  react-dom
  dva-core
    redux
    redux-saga
  history
  react-redux
  react-router-redux

実装思路

最も核心的なのは app.model メソッドを提供し、reducer、initialState、action、saga を 1 つにカプセル化すること

const model = {
    // 用作顶层 state key,以及 action 前缀
    namespace
    // module 级初始 state
    state
    // 订阅其它数据源,如 router change,window resize, key down/up...
    subscriptions
    // redux-saga 里的 sagas
    effects
    // redux 里的 reducer
    reducers
};

dva-core が実際に行う主な作業は、model 設定から reducers、worker sagas、states を取得した後、その後の一連の煩雑な作業を屏蔽 することです:

  • redux に接続(state の結合、reducer の結合)

  • redux-saga に接続(redux-saga の fork -> watcher -> worker を完了し、エラーキャプチャを準備)

core で最も重要な 2 部分以外にも、dva はいくつかのことを行っています:

  • 内蔵の react-router-redux、history がルート管理を担当

  • react-redux の connect、isomorphic-fetch などの一般的なものを貼り付け

  • subscriptions は錦上に花を添え、場外要因を監視するコードに居場所を提供

  • react と接続(store で react と redux を接続し、redux ミドルウェアメカニズムで redux-saga を引き込んで一緒にプレイ)

ここでほぼカプセル化が完了しました。では、いくつかの口を開いて少し柔軟性を追加 します:

  • 一堆のフック(effect/reducer/action/state 級 hook)を渡し、内部状態を読み取り可能に

  • グローバルエラー処理方法を提供し、非同期エラーが制御不能という痛点を解決

  • model 管理を強化(model の動的増減を許可)

実装過程はこのようだと推測:

  1. 設定化

    技術的に固化を実現し、柔軟性を制限し、ビジネス記述をより統一し���エンジニアリングの需要を満たす

  2. 通用シナリオに向けて拡張

    必要な口のみを開き、大多数のビジネスシナリオの需要を満たす最小の柔軟性セットを放出

  3. 特定の需要に向けて強化

    ビジネスの声に対応し、より多くの柔軟性を放出/提供するかどうかを考慮し、柔軟性とエンジニアリング(制御可能程度)の間で权衡取舍

三.設計理念

どのような思想に従い、どのようにしたいのか?

elmchoo から借鉴し、elm の subscription と choo の設計理念を含む

elm の subscription

いくつかのメッセージを購読して他のデータソースからデータを取得。例えば websocket connection of server, keyboard input, geolocation change, history router change など

例えば:

subscriptions: {
  setupHistory ({ dispatch, history }) {
    history.listen((location) => {
      dispatch({
        type: 'updateState',
        payload: {
          locationPathname: location.pathname,
          locationQuery: queryString.parse(location.search),
        },
      })
    })
  },
  setup ({ dispatch }) {
    dispatch({ type: 'query' })
    let tid
    window.onresize = () => {
      clearTimeout(tid)
      tid = setTimeout(() => {
        dispatch({ type: 'changeNavbar' })
      }, 300)
    }
  }
}

このメカニズムを提供して他のデータソースにアクセスし、model に集中して统一管理

choo の設計理念

choo の理念はできるだけ精简し、選択/切り替えコストをできるだけ低減:

We believe frameworks should be disposable, and components recyclable. We don't want a web where walled gardens jealously compete with one another. By making the DOM the lowest common denominator, switching from one framework to another becomes frictionless. choo is modest in its design; we don't believe it will be top of the class forever, so we've made it as easy to toss out as it is to pick up.

We don't believe that bigger is better. Big APIs, large complexities, long files - we see them as omens of impending userland complexity. We want everyone on a team, no matter the size, to fully understand how an application is laid out. And once an application is built, we want it to be small, performant and easy to reason about. All of which makes for easy to debug code, better results and super smiley faces.

大意はフレームワークは堡塁に発展すべきではなく、いつでも使用可能で使用不可能であるべき(低コスト切り替え)。API 及び設計は最小化を維持し、ユーザーに一坨の「知識」を投げない。こうすればあなたも彼(同僚)も良い

P.S. もちろん、この言葉はどこに持って行っても正しい。dva や choo 自身が達成したかどうかは言い難い(choo の実装からは堡塁を拆除する有効な措置が見えなかった)

API 設計上、dva-core はほぼ最小化を維持:

  • 1 份の model はわずか 4 つの配置項

  • API は屈指可数

  • hook はほぼすべて必須(onHmr と extraReducers は後から特定の需要に向けて強化)

しかし话说回来、dva-core が実際に行ったことは redux と redux-saga を model 設定を通じて整合し、いくつかの制御(エラー処理など)を強化するのみ。導入した唯一の外来概念は subscription で、まだ model に掛かっている。たとえ力を入れて API を設計しても、それほど複雑にはならない

四.長所と短所

どのような短所があり、もたらす収益は何か?

長所:

  • フレームワーク制限はエンジニアリングに有利。レンガのようなコードが最も良い

  • 煩雑なボイラープレートコード(boilerplate code)を簡素化。儀式のような action/reducer/saga/api...

  • 多ファイルによる注目点分散の問題を解決。ロジック分離は良いことだが、ファイル隔離は少し辛い

短所:

  • 柔軟性を制限(例えば combineReducers 問題

  • パフォーマンス負担(getSaga 部分の実装、速くなく見える、制御の目的を達成するために多くの追加のことを行った)

五.実装テクニック

外置パラメータ検査

invariant はソースコードで最も多く出現する基本套路:

function start(container) {
  // 允许 container 是字符串,然后用 querySelector 找元素
  if (isString(container)) {
    container = document.querySelector(container);
    invariant(
      container,
      `[app.start] container ${container} not found`,
    );
  }

  // 并且是 HTMLElement
  invariant(
    !container || isHTMLElement(container),
    `[app.start] container should be HTMLElement`,
  );

  // 路由必须提前注册
  invariant(
    app._router,
    `[app.start] router must be registered before app.start()`,
  );

  oldAppStart.call(app);
  //...
}

invariant は強条件を保証(条件を満たさない場合直接 throw、生産環境でも throw)。warning は弱条件を保証(開発環境で log error し [無干渉 throw](/articles/redux 源码解读/#articleHeader6)、生産環境では throw せず、空関数に交換)

invariant の無差別 throw は使用可能だが、warning は使用を推奨しない。warning を含む release コードはコンパイル置換がきれいではないため(空関数を実行する)

もう 1 つのテクニックは 1 層関数を包み、外側でパラメータ検査を行うこと。例えば示例中の:

function start(container) {
  //...参数检查
  oldAppStart.call(app);
}

このようにする利点はパラメータ検査を外に出し、可読性がより良くなること。しかし 1 層の関数呼び出しのパフォーマンスオーバーヘッドがあり、if-else 制御度ほど高くない(throw のみで後続フローを阻断可能)

切面 Hook

まずこの部分のソースコードを見る:

// 把每一个 effect 都包一遍,为了实现 effect 级的控制
const sagaWithOnEffect = applyOnEffect(onEffect, sagaWithCatch, model, key);
function applyOnEffect(fns, effect, model, key) {
  for (const fn of fns) {
    effect = fn(effect, sagaEffects, model, key);
  }
  return effect;
}

その後用法はこのよう(传入の onEffect Hook):

function onEffect(effect, { put }, model, actionType) {
  const { namespace } = model;
  return function*(...args) {
      yield put({ type: SHOW, payload: { namespace, actionType } });
      yield effect(...args);
      yield put({ type: HIDE, payload: { namespace, actionType } });
  };
}

dva-loading から抜粋)

これは环绕増強(AOP 里的 Around Advice)ではないか?

围绕一个连接点的增强,如方法调用��这是最强大的一种增强类型。环绕增强可以在方法调用前后完成自定义的行为。它也负责选择是继续执行连接点,还是直接返回它们自己的返回值或者抛出异常来结束执行

AOP(Aspect-Oriented Programming) から抜粋)

ここでの実際の作用は onEffect が saga を 1 層包み、saga の実行権を渡し、外部(onEfect hook を通じて)ロジックを注入することを許可。自分を hook に渡すことは大したテクニックではないが、用法上非常に面白い。iterator の展開可能特性を利用して、装飾者の効果を実現(saga を 1 つ渡し、増強済みの saga を受け取り、タイプが変わらずフローに影響しない)

コメント

コメントはまだありません

コメントを書く