关于前端:mobxreact中Provider和inject的理解与使用

3次阅读

共计 7589 个字符,预计需要花费 19 分钟才能阅读完成。

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

1、Provider 和 inject 应用

装置 mobx 和 mobx-react

npm i -S mobx mobx-react

创立 store

// 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 组件注入它

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

// 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,提供一个申请用于实现异步 action

// 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 接口

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')
@observer
export 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 组件更改了别名

正文完
 0