본문으로 건너뛰기

dva

무료2017-10-22#JS#dvajs#dvajs原理#dvajs入门#dvajs教程#dvajs tutorial

업계의 react&redux 모범 사례에 기반한 비즈니스 프레임워크

일.목표 위치

무엇을 해결하고자 하는가?어떻게 할 것인가?

간단히 말하면:dva업계의 react&redux 모범 사례에 기반한 비즈니스 프레임워크를 제공하여, 벌거벗은 redux全家桶을 프론트엔드 데이터 계층으로 사용함으로써 발생하는 다양한 문제를 해결하고자 합니다

  • 편집 비용이 높음, reducer, saga, action 사이를 오가야 함
  • 비즈니스 모델 (또는 도메인 모델) 조직화가 불편함. 예를 들어 userlist 를 작성한 후 productlist 를 작성하려면 많은 파일을 복사해야 함.
  • saga 작성이 너무 복잡함. action 하나를 감시할 때마다 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 를 하나로 캡슐화하는 것

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 층 함수를 감싸고, 바깥에서 파라미터 검사를 수행하는 것. 예를 들어 예시 중의:

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 하나를 건네고, 증강済みの saga 를 받아들이며, 타입이 변하지 않아 프로세스에 영향 없음)

댓글

아직 댓글이 없습니다

댓글 작성