mobx-react中Provider和inject经过context将store注入并使得任何层级的子组件能够访问到store。本文将分为两部分讲解,先说说如何使用,而后分析源码,理解底层原理。html
安装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}`) })
来看看最终效果json
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。
能够看到组件的嵌套层级变成:
为何根组件不是Provider
呢?这是由于源码中Provider.displayName = "MobXProvider";
将Provider
组件的显示名称改为了MobXProvider
。
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组件更改了别名