useModel

5,553 阅读1分钟

umi 中神奇的useModel

说明: umi会根据用户的配置生成代码,在临时文件 .umi/umi.ts 中;

umi.ts

// 记住,这俩都是 rootContainer 插件,在后面的特定时机执行;
plugin.register({
  apply: Plugin_0, // initstate
  path: '../plugin-initial-state/runtime',
})
plugin.register({
  apply: Plugin_1, // useModel
  path: '../plugin-model/runtime',
})

主流程

const getClientRender = () => plugin.applyPlugins({
  key: 'render',
  type: ApplyPluginsType.compose,
  initialValue: () => {
    const opts = plugin.applyPlugins({
      key: 'modifyClientRenderOpts',
      type: ApplyPluginsType.modify,
      initialValue: {
        routes: getRoutes(), // umi 中的路由表
        history: createHistory(), // 作为 router 的props
        rootElement: 'root',
      },
    });
    return renderClient(opts);
  },
  args,
});
// 最终执行如下: 开始真正的去渲染应用
renderClient({
  routes: getRoutes(), // umi 中的路由表
  history: createHistory(), // 作为 router 的props
  rootElement: 'root',
})
function renderClient(opts) {
  var rootContainer = plugin.applyPlugins({
    // reduce 的方式执行rootContainer插件
    type: runtime.ApplyPluginsType.modify,
    key: 'rootContainer',
    initialValue: React.createElement(RouterComponent, {
      history: opts.history,
      routes: opts.routes,
    }),
  });
  var rootElement = html模板中的root div;
  reactDom.render(rootContainer, rootElement);   
}
// 1. RouterComponent 其实就是
<Router history={createHistory()}>
  {/* 根据路由表渲染 route 组件 */}
</Router>
// 2. initstate, 这里不做介绍
// 3. useModel
const models = { '@@initialState': initialState, 文件名: 函数 }
注意: 这个models对象是解析了model文件夹然后生成的一个对象最后写入这里的

class Dispatcher {
  callbacks = {}
  data = {}
  update = namespace => {
    this.callbacks[namespace].forEach(
      callback => {
        callback(this.data[namespace])
      }
    )
  }
}

<UmiContext.Provider value={dispatcher}>
  {
    Object.entries(models).map(pair => (
      <Exe 
        key={pair[0]} 
        namespace={pair[0]} 
        hook={pair[1]} // model 函数
        onUpdate={val => {
          const [ns] = pair
          dispatcher.data[ns] = val
          dispatcher.update(ns)
        }} 
      />
    ))
  }
  // initstate router 组件
  {children} 
</UmiContext.Provider>
// Exe空组件: 目标是为了借用组件域执行自定义的hook函数
props => {
  const { hook, onUpdate, namespace } = props
  let data = hook()

  const updateRef = useRef(onUpdate);
  updateRef.current = onUpdate;
  const initialLoad = useRef(false);



  // 如果在 hook 中 setState 了, 那么会重新执行这个组件,再次执行data = hook(),得到新数据。
  // 触发 onUpdate(data) 更新dispatcher中的数据,最后触发callback,其实就是更新你用 useModel 的那个组件。
  useEffect(() => {
    // 第一次不走这里
    if (initialLoad.current) {
      updateRef.current(data)
    } else {
      initialLoad.current = true
    }
  })

  return <></>
};
// useModel 的使用
// 其实也是一个自定义的hook,作用对象是你的组件;
function useModel(
  namespace
) {
  // 这个 dispatcher 其实保存了所有的useModel的数据
  const dispatcher = useContext(UmiContext)
  
  const [state, setState] = useState(
    () => dispatcher.data[namespace]
  )
  
  
  useEffect(() => {
    const handler = e => setState(e)
    
    dispatcher.callbacks[namespace].add(handler)
    dispatcher.update(namespace)
   
    return () => dispatcher.callbacks[namespace].delete(handler)
    
  }, [namespace])

  return state
}