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組件更改了別名