mobx-react中Provider和inject通过context将store注入并使得任何层级的子组件能够拜访到store。本文将分为两局部解说,先说说如何应用,而后剖析源码,了解底层原理。

1、Provider和inject应用

装置mobx和mobx-react

npm i -S mobx mobx-react

创立store

// StyleStore.jsximport { observable, action } from "mobx";class Style {  @observable color = "red";  @observable size = 20;  @action changeColor(color) {    this.color = color;  }}export default new Style(); 
// UserStore.jsximport { 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.jsximport StyleStore from "./StyleStore";import UserStore from "./UserStore";export default {  StyleStore,  UserStore,}; 

在根组件通过Provider组件注入它

// App.jsximport 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获取store

// Parent.jsximport React from 'react'import Child from './Child'const Parent = props => {  return <Child />}export default Parent 
// Child.jsximport React from 'react'import Son from './Son'const Child = props => {  return <Son />}export default Child
// Son.jsximport React from 'react'import { observer, inject } from 'mobx-react'@inject('StyleStore', 'UserStore')@observerexport 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,提供一个申请用于实现异步action

// axios封装// request.jsximport 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
// apiimport { get } from '../service/request'export const fetchUserInfoAPI = () =>  get("/getUserInfo").then((res) => res.data); 

应用express写一个/getUserInfo接口

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}`) })

来看看最终成果

2、从源码角度剖析Provider和inject

2.1、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后的对象

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,实现如下:

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。

能够看到组件的嵌套层级变成:

为什么根组件不是Provider呢?这是因为源码中Provider.displayName = "MobXProvider";Provider组件的显示名称改成了MobXProvider

2.1、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')@observerexport default class App extends React.Component {}

打印参数列表storeNames:

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组件更改了别名