mobx-react中Provider和inject的使用與理解

mobx-react中Provider和inject經過context將store注入並使得任何層級的子組件能夠訪問到store。本文將分爲兩部分講解,先說說如何使用,而後分析源碼,理解底層原理。html

一、Provider和inject使用

安裝mobx和mobx-reactreact

npm i -S mobx mobx-react

建立storeios

// StyleStore.jsx
import { observable, action } from "mobx";

class Style {
  @observable color = "red";
  @observable size = 20;

  @action changeColor(color) {
    this.color = color;
  }
}

export default new Style();
// UserStore.jsx
import { observable, action } from "mobx";
import { fetchUserInfoAPI } from "../api/index";

class User {
  @observable user = [];
  @action async fetchUserInfo() {
    this.user = (await fetchUserInfoAPI()).data;
  }
}

export default new User();
// index.jsx
import StyleStore from "./StyleStore";
import UserStore from "./UserStore";

export default {
  StyleStore,
  UserStore,
};

在根組件經過Provider組件注入它git

// App.jsx
import React from "react";
import { render } from "react-dom";
import Parent from "./Parent";
import { Provider } from "mobx-react";
import stores from "../stores/index";

const App = (props) => {
  React.useEffect(() => {
    stores.UserStore.fetchUserInfo();
  });

  return <Parent />;
};

render(
  <Provider {...stores}>
    <App />
  </Provider>,
  document.getElementById("app")
);

在子組件中經過inject獲取storegithub

// Parent.jsx
import React from 'react'
import Child from './Child'

const Parent = props => {
  return <Child />
}

export default Parent
// Child.jsx
import React from 'react'
import Son from './Son'

const Child = props => {
  return <Son />
}

export default Child
// Son.jsx
import React from 'react'
import { observer, inject } from 'mobx-react'

@inject('StyleStore', 'UserStore')
@observer
export default class Son extends React.Component {

  render() {
    const { StyleStore, UserStore } = this.props
    return (
      <div>
        <p style={{'color': StyleStore.color, 'fontSize': StyleStore.size}}>hello, world</p>
        <button onClick={() => {StyleStore.changeColor('yellow')}}>改變文字顏色</button>
        <hr />
        <ul>
          {
            UserStore.user.map(u => <li key={u.id}>name: {u.name}, age: {u.age}</li>)
          }
        </ul>
      </div> 
    )
  }
}

另外,封裝axios,提供一個請求用於實現異步actionexpress

// axios封裝
// request.jsx
import axios from 'axios'

const service = axios.create({
  baseURL: 'http://127.0.0.1:3000',
  timeout: 450000
})

export const get = (url, params) => {
  return service({
    url, 
    method: 'GET',
    params
  })
}

export const post = (url, params) => {
  return service({
    url,
    method: 'POST',
    data: JSON.stringify(params)
  })
}

export default service
// api
import { get } from '../service/request'

export const fetchUserInfoAPI = () =>
  get("/getUserInfo").then((res) => res.data);

使用express寫一個/getUserInfo接口npm

const express = require('express')

const app = express()
const PORT = process.env.PORT || 3000

// 跨域配置
app.all('*', function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'User-Agent, Origin, Cache-Control, Content-type');
  res.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, PUT, OPTIONS, HEAD');
  res.header('Content-Type', 'application/json;charset=utf-8');
  // res.header("Access-Control-Allow-Credentials", "true");
  next();
});

app.get('/getUserInfo', (req, res) => {
  const data = [
    {
      id: Math.random(),
      name: '張三',
      age: 20
    },
    {
      id: Math.random(),
      name: '李四',
      age: 23
    }
  ]
  res.json({ code: 0, status: 200, data })
})

app.listen(PORT, () => { console.log(`server is listening port ${PORT}`) })

來看看最終效果
mob-react.gifjson

二、從源碼角度分析Provider和inject

2.一、Provider源碼分析

var MobXProviderContext =
/*#__PURE__*/
React__default.createContext({});
function Provider(props) {
  var children = props.children,
      stores = _objectWithoutPropertiesLoose(props, ["children"]); // 獲取除去children後的props對象

  var parentValue = React__default.useContext(MobXProviderContext);
  // `useRef`返回一個可變的 ref 對象,其`.current`屬性被初始化爲傳入的參數(`initialValue`)。返回的 ref 對象在組件的整個生命週期內保持不變。
  var mutableProviderRef = React__default.useRef(_extends({}, parentValue, {}, stores));
  var value = mutableProviderRef.current;

  if (process.env.NODE_ENV !== "production") {
    var newValue = _extends({}, value, {}, stores); // spread in previous state for the context based stores


    if (!shallowEqual(value, newValue)) {
      throw new Error("MobX Provider: The set of provided stores has changed. See: https://github.com/mobxjs/mobx-react#the-set-of-provided-stores-has-changed-error.");
    }
  }

  return React__default.createElement(MobXProviderContext.Provider, {
    value: value
  }, children);
}
Provider.displayName = "MobXProvider";

_objectWithoutPropertiesLoose函數用於獲取Provider組件props除去children後的對象axios

function _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

_extends其實就是Object.assign,實現以下:api

function _extends() {
  _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }

    return target;
  };

  return _extends.apply(this, arguments);
}
var mutableProviderRef = React__default.useRef(_extends({}, parentValue, {}, stores));
var value = mutableProviderRef.current;

這兩行代碼來理解一下,若是你還不瞭解useRef鉤子函數的使用先去官網看看,傳送門:https://react.docschina.org/d...useRef返回一個可變的 ref 對象,其.current屬性被初始化爲傳入的參數(initialValue)。返回的 ref 對象在組件的整個生命週期內保持不變。這裏使用ref對象current屬性來存儲store的好處是useRef會在每次渲染時返回同一個 ref 對象,並且current屬性的改變不會引發組件從新渲染。

return React__default.createElement(MobXProviderContext.Provider, {
    value: value
  }, children);

從上面代碼就能看出Provider組件的核心仍是使用context來向子孫組件傳遞store。

能夠看到組件的嵌套層級變成:
image.png

爲何根組件不是Provider呢?這是由於源碼中Provider.displayName = "MobXProvider";Provider組件的顯示名稱改爲了MobXProvider

2.一、inject源碼分析

function inject() {
  for (var _len = arguments.length, storeNames = new Array(_len), _key = 0; _key < _len; _key++) {
    storeNames[_key] = arguments[_key];
  }

  if (typeof arguments[0] === "function") {
    var grabStoresFn = arguments[0];
    return function (componentClass) {
      return createStoreInjector(grabStoresFn, componentClass, grabStoresFn.name, true);
    };
  } else {
    return function (componentClass) {
      return createStoreInjector(grabStoresByName(storeNames), componentClass, storeNames.join("-"), false);
    };
  }
}

inject函數實際上是一個高階組件,返回的是一個函數組件

function (componentClass) {
  // return 返回的是一個組件 
  return createStoreInjector(grabStoresByName(storeNames), componentClass, storeNames.join("-"), false);
};

inject函數中先將函數參數數組copy到storeNames數組中,而後判斷函數的第一個參數是否是Function類型,若是是,則返回

function (componentClass) {
  return createStoreInjector(grabStoresFn, componentClass, grabStoresFn.name, true);
};

若是不是,則返回

return function (componentClass) {
  return createStoreInjector(grabStoresByName(storeNames), componentClass, storeNames.join("-"), false);
};

當咱們使用修飾器方式@inject,inject執行上面第二種狀況;當使用inject(Function),inject執行上面第一種狀況,下面以修飾器方式爲例繼續講解。

@inject('StyleStore', 'UserStore')
@observer
export default class App extends React.Component {}

打印參數列表storeNames:
image.png

function grabStoresByName(storeNames) {
  return function (baseStores, nextProps) {
    storeNames.forEach(function (storeName) {
      if (storeName in nextProps // prefer props over stores
      ) return;
      if (!(storeName in baseStores)) throw new Error("MobX injector: Store '" + storeName + "' is not available! Make sure it is provided by some Provider");
      nextProps[storeName] = baseStores[storeName];
    });
    return nextProps;
  };
}

在調用createStoreInjector時會執行grabStoresByName函數,該函數返回一個函數,用於將@inject('xxx', 'xxx')中想到注入的對象從store中取出copy到組件的props對象中。baseStores參數就是使用useContext鉤子獲取的上下文對象。

function createStoreInjector(grabStoresFn, component, injectNames, makeReactive) {
  // React.forwardRef 用於轉發ref,並返回一個新組件
  var Injector = React__default.forwardRef(function (props, ref) {
    var newProps = _extends({}, props);

    var context = React__default.useContext(MobXProviderContext);
    Object.assign(newProps, grabStoresFn(context || {}, newProps) || {});

    if (ref) {
      newProps.ref = ref;
    }

    return React__default.createElement(component, newProps);
  });
  if (makeReactive) Injector = observer(Injector);
  Injector["isMobxInjector"] = true; // assigned late to suppress observer warning
  // Static fields from component should be visible on the generated Injector

  copyStaticProperties(component, Injector);
  Injector["wrappedComponent"] = component;
  Injector.displayName = getInjectName(component, injectNames);
  return Injector;
}

createStoreInjector函數使用forwardRef鉤子返回一個新組件(React.forwardRef),並將接受到的ref以及獲取的store經過props注入到@inject修飾的類組件中。

Injector.displayName = getInjectName(component, injectNames);

Injector組件更改了別名
image.png

參考:
https://zh-hans.reactjs.org/d...

相關文章
相關標籤/搜索