1. 程式人生 > >ant design pro 程式碼學習(六) ----- 知識點總結2

ant design pro 程式碼學習(六) ----- 知識點總結2

1 、connect 多個model

  以下為redux的API中對connect方法的定義:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

  實際過程中使用最多的是mapStateToProps(另外兩個引數暫時不做討論),如果定義該引數,元件將會監聽 Redux store 的變化。任何時候,只要 Redux store 發生改變,mapStateToProps 函式就會被呼叫。該回調函式必須返回一個純物件,這個物件會與元件的 props 合併。

  以BasicLayout元件的connect為例,程式碼如下:

export default connect(({ user, global, loading }) => ({
  currentUser: user.currentUser,
  collapsed: global.collapsed,
  fetchingNotices: loading.effects['global/fetchNotices'],
  notices: global.notices,
})
)(BasicLayout);

  為便於理解,對以上程式碼稍作更改。如下所示:

function mapStateToProps(store) {
  console.log(store);
  const
{user, global, loading} = store return { currentUser: user.currentUser, collapsed: global.collapsed, fetchingNotices: loading.effects['global/fetchNotices'], notices: global.notices, } } export default connect(mapStateToProps)(BasicLayout);

  此時控制檯中,將會打印出所有已經註冊過的model。(此處ant design pro程式碼有一個bug,下邊會詳細分析)

。其中可以獲取多個管理model的資料,即可以將多個model的資料(state)註冊到當前元件的props中。。

2 、dva之loading狀態

  dva-loading的使用非常簡單,在index.js中加入:

import createLoading from 'dva-loading';
app.use(createLoading());

  每個頁面中將loading狀態作為屬性(props)傳入元件,在進行樣式處理,比如轉圈圈或者顯示正在載入什麼的,但是重點是,我們的app有多個頁面,每個頁面都這麼做,很繁瑣。如何只做一次狀態處理,每次請求期間都會觸發loading狀態呢,其實也很簡單啦,因為dva-loading提供了一個global屬性。

  可能有個疑問:既然 global 可以展示非同步載入是否完成,為什麼還要 effects 屬性,這是因為一個頁面中可能同時有多個非同步載入,只要有一個非同步載入沒有完成,global 都是 true,但是 effects中載入完成的非同步方法都會變成false,只有沒載入完成的非同步方法也會是 true。

  loading的注入:

@connect(({login, loading}) => {
    return {
      login,
      submitting: loading.effects['login/login'],
    }
  }
)

  loading的屬性:

loading: {
  global: false,
  models: {app: false},
  effects: {app: false}
}

  loading.effects是一個物件,對應的引數是:model下的effect,值是true或false,表示該effect是否被呼叫

3 、動態載入元件dynamicWrapper(ant design pro 的bug)


const dynamicWrapper = (app, models, component) => {
  // () => require('module')
  // transformed by babel-plugin-dynamic-import-node-sync
  if (component.toString().indexOf('.then(') < 0) {
    models.forEach(model => {
      if (modelNotExisted(app, model)) {
        // eslint-disable-next-line
        app.model(require(`../models/${model}`).default);
      }
    });

    return props => {
      if (!routerDataCache) {
        routerDataCache = getRouterData(app);
      }
      return createElement(component().default, {
        ...props,
        routerData: routerDataCache,
      });
    };
  }
  // () => import('module')
  return dynamic({
    app,
    models: () =>
      models.filter(model => modelNotExisted(app, model)).map(m => import(`../models/${m}.js`)),
    // add routerData prop
    component: () => {
      if (!routerDataCache) {
        routerDataCache = getRouterData(app);
      }
      return component().then(raw => {
        const Component = raw.default || raw;
        return props =>
          createElement(Component, {
            ...props,
            routerData: routerDataCache,
          });
      });
    },
  });
};

  引用的地方程式碼如下:

component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),

  在實際的執行過程中,並沒有實現按需載入,所有的model都在路由生成時註冊啦(執行if部分程式碼)。已下例為證明:稍微修改了一下BasicLayout的connect方法

function mapStateToProps(store) {
  console.log(store);
  const {user, global, loading} = store
  return {
    currentUser: user.currentUser,
    collapsed: global.collapsed,
    fetchingNotices: loading.effects['global/fetchNotices'],
    notices: global.notices,
  }
}

export default connect(mapStateToProps)(BasicLayout);

  此時console.log輸出的所有model。如果按需載入的話,此時store中的model,應該只包含BasicLayout依賴的model— [‘user’, ‘login’]

  實際上所有的專案涉及的model都已經被註冊。如圖所示

  111

  ==因此建議dynamicWrapper方法修改如下:==

const dynamicWrapper = (app, models, component) => {
  return dynamic({
    app,
    models: () =>
      models.filter(model => modelNotExisted(app, model)).map(m => import(`../models/${m}.js`)),
    // add routerData prop
    component: () => {
      if (!routerDataCache) {
        routerDataCache = getRouterData(app);
      }
      return props =>
        createElement(component().default, {
          ...props,
          routerData: routerDataCache,
        });
    },
  });
};

  修改的model載入如下:

  222

4 、裝飾器

  修飾器是一個對類進行處理的函式。修飾器函式的第一個引數,就是所要修飾的目標類。修飾器對類的行為的改變,是程式碼編譯時發生的,而不是在執行時。這意味著,修飾器能在編譯階段執行程式碼。也就是說,修飾器本質就是編譯時執行的函式。

  實際開發中,React 與 Redux 庫結合使用時,常常需要寫成下面這樣。

class MyReactComponent extends React.Component {}

export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);

  有了裝飾器,就可以改寫上面的程式碼。

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}

  1. 修飾器不僅可以修飾類,還可以修飾類的屬性。

  2. 如果同一個方法有多個修飾器,會像剝洋蔥一樣,先從外到內進入,然後由內向外執行。

  3. 除了註釋,修飾器還能用來型別檢查。所以,對於類來說,這項功能相當有用。從長期來看,它將是 JavaScript 程式碼靜態分析的重要工具。

  4. 修飾器只能用於類和類的方法,不能用於函式,因為存在函式提升

5、Fragment

  React 中一個常見模式是為一個元件返回多個元素。 片段(fragments) 可以讓你將子元素列表新增到一個分組中,並且不會在DOM中增加額外節點。
某些HTML元素只能接受特定的元素作為子節點,例如tr只能接受td ,如果將這些特定的子節點封裝成元件的話,必須加入div包裹,那麼最終生成的 HTML 將是無效的。

  React.Fragment將這些特定元素包裹,不會產生額外影響,改功能類似於Vue中的template。

6、context

In Some Cases, you want to pass data through the component tree without having to pass the props down manuallys at every level. you can do this directly in React with the powerful “context” API.

  當你不想在元件樹中通過逐層傳遞props或者state的方式來傳遞資料時,可以使用Context來實現跨層級的元件資料傳遞。

  使用props或者state傳遞資料,資料自頂下流。

  image

  使用Context,可以跨越元件進行資料傳遞。

  image

  如果要Context發揮作用,需要用到兩種元件,一個是Context生產者(Provider),通常是一個父節點,另外是一個Context的消費者(Consumer),通常是一個或者多個子節點。所以Context的使用基於生產者消費者模式

  對於父元件,也就是Context生產者,需要通過一個靜態屬性childContextTypes宣告提供給子元件的Context物件的屬性,並實現一個例項getChildContext方法,返回一個代表Context的純物件 (plain object) 。對於子元件,也就是Context使用者,需要通過一個靜態屬性contextTypes聲明後,才能訪問父元件Context物件的屬性,否則,即使屬性名沒寫錯,拿到的物件也是undefined。

  通過childContextTypes和contextTypes這兩個靜態屬性的約束,可以在一定程度保障,只有元件自身,或者是與元件相關的其他子元件才可以隨心所欲的訪問Context的屬性,無論是資料還是函式。因為只有元件自身或者相關的子元件可以清楚它能訪問Context哪些屬性,而相對於那些與元件無關的其他元件,無論是內部或者外部的 ,由於不清楚父元件鏈上各父元件的childContextTypes“宣告”了哪些Context屬性,所以沒法通過contextTypes“申請”相關的屬性。所以我理解為,給元件的作用域Context“帶許可權”,可以在一定程度上確保Context的可控性和影響範圍。

  對於無狀態子元件(Stateless Component),可以通過如下方式訪問父元件的Context

import React from 'react'
import PropTypes from 'prop-types'

const ChildComponent = (props, context) => {
 ......
}

ChildComponent.contextProps = {
  ......  
}

  以Login元件為例:父元件中什麼childContextTypes物件,並通過getChildContext()返回一個物件。程式碼如下:

class Login extends Component {

    static childContextTypes = {
        tabUtil: PropTypes.object,
        form: PropTypes.object,
        updateActive: PropTypes.func,
      };

    getChildContext() {
        return {
          tabUtil: {.....},
          form: this.props.form,
          updateActive: activeItem => {......},
        };
  }
}

  在子元件中使用,首先宣告contextTypes,程式碼如下:

export default class LoginTab extends Component {
    static contextTypes = {
        tabUtil: PropTypes.object,
    };

    componentWillMount() {
        if (this.context.tabUtil) {
          this.context.tabUtil.addTab(this.uniqueId);
        }
    }
}