Technical difficulties of qiankun framework

Do the routes of sub-applications have to be added with the corresponding prefix?

No, just check whether it is in qiankun. If it is, add the prefix. If not, don't add it.

qiankun's props parameters

If we write props in micro-app.js of the main application, the parameters of the mount hook function of our sub-application will get an object containing props. Why are there other properties besides props data? This is because qiankun Help us deconstruct the props data into an object. This object contains the following six properties. This object is the parameter we finally print in the sub-application mount.
Insert image description here

Where to distinguish between development environment and production environment

1. The micro-app.js of the main application needs to be distinguished, specifically in the entry of the microApps array.
Insert image description here
2. The routing configuration of the sub-application needs to be distinguished from the environment.
Insert image description here

getGlobalStateDesign

If we use the official example directly, the data will be loose and the call will be complicated. All sub-applications must declare onGlobalStateChange to monitor the state, and then update the data through setGlobalState.

Therefore, it is necessary for us to further encapsulate the data state. The author mainly considers the following points here:

The main application should be kept simple and simple. For sub-applications, the data delivered by the main application is a very pure object to better support sub-applications of different frameworks. Therefore, the main application does not need to use vuex.

The Vue child application must be able to inherit the data sent by the parent application and support independent operation.

The sub-application can obtain the latest data issued by the main application during the mount declaration cycle, and then register this data in a vuex module named global. The sub-application updates the data through the action of the global module. When updating, Automatically syncs back to parent app.

Initialization state file of the main application

store.js

import {
    
     initGlobalState } from 'qiankun'
import Vue from 'vue'

// 父应用的初始state
// Vue.observable是为了让initialState变成可响应:https://cn.vuejs.org/v2/api/#Vue-observable。
const initialState = Vue.observable({
    
    
  user: {
    
    
    name: 'zhangsan'
  }
})

const actions = initGlobalState(initialState)

actions.onGlobalStateChange((newState, prev) => {
    
    
  // state: 变更后的状态; prev 变更前的状态
  console.log('main change', JSON.stringify(newState), JSON.stringify(prev))

  for (const key in newState) {
    
    
    initialState[key] = newState[key]
  }
})

// 定义一个获取state的方法下发到子应用
actions.getGlobalState = (key) => {
    
    
  // 有key,表示取globalState下的某个子级对象
  // 无key,表示取全部

  return key ? initialState[key] : initialState
}

export default actions

Note:
1. Vue.observable is to make the data responsive. If it is not added, the data will change, but the page will not be refreshed. This is mainly to achieve the requirement that the sub-application in the base modifies the state and the base refreshes the menu bar.
2. Encapsulate the getGlobalState method to obtain state, implement onGlobalStateChange data monitoring and change (it will be triggered when setting data),
and then this getGlobalState method is sent to the sub-application through props.

State encapsulation of vue sub-applications

As mentioned earlier, when the child application mounts, it will register the state delivered by the parent application as a vuex module called global. To facilitate reuse, we encapsulate it:

// sub-vue/src/store/global-register.js

/**
 * 
 * @param {vuex实例} store 
 * @param {qiankun下发的props} props 
 */
function registerGlobalModule(store, props = {
     
     }) {
    
    
  if (!store || !store.hasModule) {
    
    
    return;
  }

  // 获取初始化的state
  const initState = props.getGlobalState && props.getGlobalState() || {
    
    
    menu: [],
    user: {
    
    }
  };

  // 将父应用的数据存储到子应用中,命名空间固定为global
  if (!store.hasModule('global')) {
    
    
    const globalModule = {
    
    
      namespaced: true,
      state: initState,
      actions: {
    
    
        // 子应用改变state并通知父应用
        setGlobalState({
     
      commit }, payload) {
    
    
          commit('setGlobalState', payload);
          commit('emitGlobalState', payload);
        },
        // 初始化,只用于mount时同步父应用的数据
        initGlobalState({
     
      commit }, payload) {
    
    
          commit('setGlobalState', payload);
        },
      },
      mutations: {
    
    
        setGlobalState(state, payload) {
    
    
          // eslint-disable-next-line
          state = Object.assign(state, payload);
        },
        // 通知父应用
        emitGlobalState(state) {
    
    
          if (props.setGlobalState) {
    
    
            props.setGlobalState(state);
          }
        },
      },
    };
    store.registerModule('global', globalModule);
  } else {
    
    
    // 每次mount时,都同步一次父应用数据
    store.dispatch('global/initGlobalState', initState);
  }
};

export default registerGlobalModule;

Add the use of global-module to main.js:
trigger this function when the sub-application is mounted to save the delivered data into vuex

import globalRegister from './store/global-register'

export async function mount(props) {
    
    
  console.log('[vue] props from main framework', props)
  globalRegister(store, props)
  render(props)
}

ps: This solution also has shortcomings, because the child application will synchronize the state issued by the parent application only when it is mounted. Therefore, it is only suitable for architectures that only mount one child application at a time (not suitable for the coexistence of multiple child applications); if the parent application data changes but the child application does not trigger mount, the latest data of the parent application cannot be synchronized back to the child application. If you want to achieve the coexistence of multiple child applications and the dynamic transmission of children from the parent, the child applications still need to use the onGlobalStateChange API monitoring provided by qiankun. Students who have better solutions can share and discuss them. This solution just meets the author's current project needs, so it is sufficient. Students are asked to package it according to their own business needs.

Click on the sub-application running on the dock to jump to another sub-application, and also switch the top menu bar of the dock

Requirements:
In addition to clicking the menu at the top of the page to switch sub-applications, our requirements also require that the sub-application jumps to other sub-applications. This will involve the display problem of the active state of the top menu: sub-vue switches to sub-react. At this time, the top menu Sub-react needs to be changed to active state.
Since qiankun currently does not encapsulate the API for child applications to throw events to the parent application, the parent application is used to monitor the history.pushState event. When it is discovered that the route has been changed, the parent application will know whether to change the activation state.

Analysis:
The sub-application jumps through history.pushState(null, '/sub-react', '/sub-react'), so the parent application can find a way to monitor history.pushState when it is mounted. Since history.popstate can only monitor back/forward/go but cannot monitor history.pushState, an additional global copy of the history.pushState event is required.

Note: Why not use other jump methods?
Jumping to the a tag will refresh the page, and the original status will be lost.
Using window.location.href to jump will cause a fleeting white screen to appear. The experience is not good, and parameters cannot be passed
using this.$router.push(). The jump will bring the original routerbase, which is only suitable for controlling routing jumps between sub-applications, not suitable for jumps between applications.

Solution

Jump through the history.pushState() method.
This method will not refresh the page, but will only change the URL
problem: although this can solve the problem of jumping to sub-applications within the base, it cannot expose routing change events to the base. The base cannot Knowing that the route has jumped, a problem is that the child application has jumped, but the top menu bar of the base has not been switched, as shown below.
Insert image description here
Solution: The parent application can find a way to monitor the history.pushState when it is mounted. Since there is no onpushState event, an additional global copy of the history.pushState event is required. Then use addEventListener to listen

// main/src/App.vue
export default {
    
    
  methods: {
    
    
    bindCurrent () {
    
    
      const path = window.location.pathname
      if (this.microApps.findIndex(item => item.activeRule === path) >= 0) {
    
    
        this.current = path
      }
    },
    listenRouterChange () {
    
    
      const _wr = function (type) {
    
    
        const orig = history[type]
        return function () {
    
    
          const rv = orig.apply(this, arguments)
          const e = new Event(type)
          e.arguments = arguments
          window.dispatchEvent(e)
          return rv
        }
      }
      history.pushState = _wr('pushState')

      window.addEventListener('pushState', this.bindCurrent)
      window.addEventListener('popstate', this.bindCurrent)

      this.$once('hook:beforeDestroy', () => {
    
    
        window.removeEventListener('pushState', this.bindCurrent)
        window.removeEventListener('popstate', this.bindCurrent)
      })
    }
  },
  mounted () {
    
    
    this.listenRouterChange()
  }
}

Analyze the implementation principle of onpushState

  const _wr = function (type) {
    
    
    const orig = history[type]
    return function () {
    
    
      console.log(this,'11111111111111111')
      const rv = orig.apply(this,arguments)
      const e = new Event(type)
      e.arguments = arguments
      window.dispatchEvent(e)
      return rv
    }
  }
  history.pushState = _wr('pushState')

  window.addEventListener('pushState', this.bindCurrent)

First of all, why should it be written in the form of a closure? Can't it be written directly as const rv = history[type].apply(this,arguments)?
No, because careful analysis found that when we execute the code history.pushState(null, '', '/aaa'), the _wr function return function will be called, which is the function that executes return, so if it is written like that, execute When history[type].apply(this,arguments), the history.pushState event will be triggered again and the return function will be executed, thus creating an infinite loop. Writing a closure will only execute apply once, and the history.pushState called this time is not rewritten, but the original history.pushState of the closure, so there will be no loop.

Secondly, this in apply here points to history. The reason is that when the return function is called, it is in the form history.pushState(null, '', '/aaa'). This is the history object. New Event is a custom event object
. window.dispatchEvent(e) triggers this event. That is to say, every time history.pushState(null, '', '/aaa') is executed, dispatchEvent will trigger the event, thus realizing monitoring.

Do I need routing files in the base?

No, how to redirect?
How to make http://localhost:8080/ open and automatically access http://localhost:8080/sub-vue?
Just use Qiankun's setDefaultMountApp.
Introduce setDefaultMountApp into main.js
and specify the default loaded sub-application between lines 49.
Insert image description here

The default activation effect of the top menu

Requirements: When we open a sub-application of the base for the first time, the menu bar needs to get which sub-application it is and set the activated class name.
Write a method in the APP.vue of the base.

bindCurrent () {
    
    
  const path = window.location.pathname
  if (this.microApps.findIndex(item => item.activeRule === path) >= 0) {
    
    
    this.current = path
  }

Just call the created hook function.
Menu navigation
item.activeRule === current plus class name

  <ul class="sub-apps">
    <li v-for="item in microApps" :class="{active: item.activeRule === current}" :key="item.name" @click="goto(item)">{
    
    {
    
     item.name }}</li>
  </ul>

Guess you like

Origin blog.csdn.net/wyh666csdn/article/details/128651302