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组件更改了别名
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。