整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          vue3源碼分析-實(shí)現(xiàn)組件的掛載流程


          <<往期回顧>>

          1. 手寫vue3源碼——創(chuàng)建項目
          2. 手寫vue3源碼——reactive, effect ,scheduler, stop
          3. 手寫vue3源碼——readonly, isReactive,isReadonly, shallowReadonly
          4. 手寫vue3源碼——ref, computed
          5. vue3源碼分析——rollup打包monorepo

          接下來一起學(xué)習(xí)下,runtime-core里面的方法,本期主要實(shí)現(xiàn)的內(nèi)容是,通過createApp方法,到mount最后把咋們的dom給掛載成功!,所有的源碼請查看

          效果

          咋們需要使這個測試用例跑成功!,在圖中可以發(fā)現(xiàn),調(diào)用app傳入了一個render函數(shù),然后掛載,對比期望結(jié)果!

          測試dom

          思考再三,先把這一節(jié)先說了,jest是怎么來測試dom的?

          jest默認(rèn)的環(huán)境是node,在jest.config.js中可以看到

          npm有在node中實(shí)現(xiàn)了瀏覽器環(huán)境的api的庫,jsdom、happy-dom 等,咋們這里就使用比較輕的happy-dom,但是happy-dom里面與jest結(jié)合是一個子包——@happy-dom/jest-environment,那就安裝一下

          pnpm add @happy-dom/jest-environment -w -D
          復(fù)制代碼

          由于我項目示例使用的是monorepo,所以只需要在runtime-core中進(jìn)行以下操作即可:

          在jest.config.js中修改環(huán)境

           testEnvironment: '@happy-dom/jest-environment',
          復(fù)制代碼

          然后你就可以在當(dāng)前子包中使用正確運(yùn)行測試用例了。

          小問題

          1. 全局的package.json運(yùn)行的時候報錯,內(nèi)容是沒有dom環(huán)境
          2. vscode 插件 jest自動運(yùn)行失敗

          針對第一個問題,在上一節(jié)vue3源碼分析——rollup打包monorepo中我們可以知道,在全局可以執(zhí)行packages中的每一個腳本,同理,我們做以下操作:

          // 在全局的package.json中的test修改成這句話
           "test": "pnpm -r --filter=./packages/** run test",
          復(fù)制代碼

          那么就可以執(zhí)行啦!

          第二個問題,這個是vscode的插件問題,我們可以重jest插件的文檔入手,可以發(fā)現(xiàn)jest執(zhí)行的時候,可以自定義腳本,解決辦法如下:

          意思是說,jest自動執(zhí)行的時候,直接執(zhí)行我們項目的test腳本,由于第一個問題的解決,第二個問題也是ok的哦!

          正文

          在正文之前,希望您先看過本系列文章的 vue3 組件初始化流程,這里詳細(xì)介紹了組件的初始化流程,這里主要是實(shí)現(xiàn)掛載

          測試用例

          describe('apiCreateApp', () => {
          // 定義一個跟節(jié)點(diǎn)
            let appElement: Element;
            // 開始之前創(chuàng)建dom元素
            beforeEach(() => {
              appElement = document.createElement('div');
              appElement.id = 'app';
              document.body.appendChild(appElement);
            });
          // 執(zhí)行完測試后,情況html內(nèi)部的內(nèi)容
            afterEach(() => {
              document.body.innerHTML = '';
            });
          
            test('測試createApp,是否正確掛載', () => {
            // 調(diào)用app方法,傳入render函數(shù)
              const app = createApp({
                render() {
                  return h('div', {}, '123');
                }
              });
              const appDoc = document.querySelector('#app')
              // 調(diào)用mount函數(shù)
              app.mount(appDoc);
              expect(document.body.innerHTML).toBe('<div id="app"><div>123</div></div>');
            })
          })
          
          復(fù)制代碼

          流程圖

          1. 一開始需要createApp,那咋們就給一個,并且返回一個mount函數(shù)
          function createApp(rootComponent) {
            const app = {
              _component: rootComponent,
              mount(container) {
                const vnode = createVNode(rootComponent);
                render(vnode, container);
              }
            };
            return app;
          }
          復(fù)制代碼
          1. mount內(nèi)部需要創(chuàng)建vnode的方法,咋們也給一個,并且把跟組件作為參數(shù)傳入
          function createVNode(type, props, children) {
           // 一開始咋們就是這么簡單,vnode里面有一個type,props,children這幾個關(guān)鍵的函數(shù)
            const vnode = {
              type,
              props: props || {},
              children: children || []
            };
            return vnode;
          }
          復(fù)制代碼
          1. 需要render函數(shù),咋們也來創(chuàng)建一個,并且內(nèi)容只調(diào)用了patch,咋把這兩個一起創(chuàng)建
          function render(vnode, container) {
            patch(vnode, container);
          }
          
          function patch(vnode, container) {
          // patch需要判斷vnode的type,如果是對象,則是處理組件,如果是字符串div,p等,則是處理元素
            if (isObj(vnode.type)) {
              processComponent(null, vnode, container);
            } else if (String(vnode.type).length > 0) {
              processElement(null, vnode, container);
            }
          }
          復(fù)制代碼
          1. 咋們先處理組件吧,創(chuàng)建一個processComponent函數(shù)
          // n1 是老節(jié)點(diǎn),n2則是新節(jié)點(diǎn),container是掛載的容器
          function processComponent(n1, n2, container) {
          // 如果n1不存在,直接是掛載組件
            if (!n1) {
              mountComponent(n2, container);
            }
          }
          復(fù)制代碼
          1. 創(chuàng)建mountComponent方法來掛載組件
          function mountComponent(vnode, container) {
            // 創(chuàng)建組件實(shí)例
            const instance = createComponentInstance(vnode);
            // 處理組件,初始化setup,slot,props, render等在實(shí)例的掛載
            setupComponent(instance);
            // 執(zhí)行render函數(shù)
            setupRenderEffect(instance, vnode, container);
          }
          復(fù)制代碼
          1. 創(chuàng)建組件的實(shí)例createComponentInstance
          // 是不是組件實(shí)例很簡單,就只有一個vnode,props,
          function createComponentInstance(vnode) {
            const instance = {
              vnode,
              props: {},
              type: vnode.type
            };
            return instance;
          }
          復(fù)制代碼
          1. 處理組件的狀態(tài), 這個函數(shù)里面會比較多內(nèi)容
          function setupComponent(instance) {
            const { props } = instance;
            // 初始化props
            initProps(instance, props);
            // 處理組件的render函數(shù)
            setupStatefulComponent(instance);
          }
          function setupStatefulComponent(instance) {
            const Component = instance.type;
            const { setup } = Component;
            // 是否存在setup
            if (setup) {
              const setupResult = setup();
              // 處理setup的結(jié)果
              handleSetupResult(instance, setupResult);
            }
            // 完成render在instance中
            finishComponentSetup(instance);
          }
          
          function handleSetupResult(instance, setupResult) {
          // 函數(shù)作為instance的render函數(shù)
            if (isFunction(setupResult)) {
              instance.render = setupResult;
            } else if (isObj(setupResult)) {
              instance.setupState = proxyRefs(setupResult);
            }
            finishComponentSetup(instance);
          }
          function finishComponentSetup(instance) {
            const Component = instance.type;
            // 如果沒有的話,直接使用Component的render
            if (!instance.render) {
              instance.render = Component.render;
            }
          }
          復(fù)制代碼
          1. 創(chuàng)建setupRenderEffect,執(zhí)行實(shí)例的render函數(shù)
          function setupRenderEffect(instance, vnode, container) {
            const subtree = instance.render();
            patch(subtree, container);
          }
          復(fù)制代碼
          1. 處理完組件,接下來該處理元素了 processElement
          // 這個方法和processComponent一樣
          function processElement(n1, n2, container) {
          // 需要判斷是更新還是掛載
            if (n1) ; else {
              mountElement(n2, container);
            }
          }
          復(fù)制代碼
          1. 掛載元素 mountElement
          function mountElement(vnode, container) {
          // 創(chuàng)建根節(jié)點(diǎn)
            const el = document.createElement(vnode.type);
            const { props } = vnode;
            // 掛載屬性
            for (let key in props) {
              el.setAttribute(key, props[key]);
            }
            const children = vnode.children;
            // 如果children是數(shù)組,繼續(xù)patch
            if (Array.isArray(children)) {
              children.forEach((child) => {
                patch(child, el);
              });
            } else if (String(children).length > 0) {
              el.innerHTML = children;
            }
            // 把元素掛載到根節(jié)點(diǎn)
            container.appendChild(el);
          }
          復(fù)制代碼

          恭喜,到這兒就完成本期的內(nèi)容,重頭看一下,vue組件的掛載分為兩種,處理組件和處理元素,最終回歸到處理元素上面,最后實(shí)現(xiàn)節(jié)點(diǎn)的掛載,該內(nèi)容是經(jīng)過非常多刪減,只是為了實(shí)現(xiàn)一個基本掛載,還有許多的邊界都沒有完善,后續(xù)繼續(xù)加油???

          一、思考

          我們都聽過知其然知其所以然這句話

          那么不知道大家是否思考過new Vue()這個過程中究竟做了些什么?

          過程中是如何完成數(shù)據(jù)的綁定,又是如何將數(shù)據(jù)渲染到視圖的等等

          #一、分析

          首先找到vue的構(gòu)造函數(shù)

          源碼位置:src\core\instance\index.js

          function Vue (options) {
            if (process.env.NODE_ENV !== 'production' &&
              !(this instanceof Vue)
            ) {
              warn('Vue is a constructor and should be called with the `new` keyword')
            }
            this._init(options)
          }
          


          options是用戶傳遞過來的配置項,如data、methods等常用的方法

          vue構(gòu)建函數(shù)調(diào)用_init方法,但我們發(fā)現(xiàn)本文件中并沒有此方法,但仔細(xì)可以看到文件下方定定義了很多初始化方法

          initMixin(Vue);     // 定義 _init
          stateMixin(Vue);    // 定義 $set $get $delete $watch 等
          eventsMixin(Vue);   // 定義事件  $on  $once $off $emit
          lifecycleMixin(Vue);// 定義 _update  $forceUpdate  $destroy
          renderMixin(Vue);   // 定義 _render 返回虛擬dom
          


          首先可以看initMixin方法,發(fā)現(xiàn)該方法在Vue原型上定義了_init方法

          源碼位置:src\core\instance\init.js

          Vue.prototype._init = function (options?: Object) {
              const vm: Component = this
              // a uid
              vm._uid = uid++
              let startTag, endTag
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                startTag = `vue-perf-start:${vm._uid}`
                endTag = `vue-perf-end:${vm._uid}`
                mark(startTag)
              }
          
              // a flag to avoid this being observed
              vm._isVue = true
              // merge options
              // 合并屬性,判斷初始化的是否是組件,這里合并主要是 mixins 或 extends 的方法
              if (options && options._isComponent) {
                // optimize internal component instantiation
                // since dynamic options merging is pretty slow, and none of the
                // internal component options needs special treatment.
                initInternalComponent(vm, options)
              } else { // 合并vue屬性
                vm.$options = mergeOptions(
                  resolveConstructorOptions(vm.constructor),
                  options || {},
                  vm
                )
              }
              /* istanbul ignore else */
              if (process.env.NODE_ENV !== 'production') {
                // 初始化proxy攔截器
                initProxy(vm)
              } else {
                vm._renderProxy = vm
              }
              // expose real self
              vm._self = vm
              // 初始化組件生命周期標(biāo)志位
              initLifecycle(vm)
              // 初始化組件事件偵聽
              initEvents(vm)
              // 初始化渲染方法
              initRender(vm)
              callHook(vm, 'beforeCreate')
              // 初始化依賴注入內(nèi)容,在初始化data、props之前
              initInjections(vm) // resolve injections before data/props
              // 初始化props/data/method/watch/methods
              initState(vm)
              initProvide(vm) // resolve provide after data/props
              callHook(vm, 'created')
          
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                vm._name = formatComponentName(vm, false)
                mark(endTag)
                measure(`vue ${vm._name} init`, startTag, endTag)
              }
              // 掛載元素
              if (vm.$options.el) {
                vm.$mount(vm.$options.el)
              }
            }
          


          仔細(xì)閱讀上面的代碼,我們得到以下結(jié)論:

          • 在調(diào)用beforeCreate之前,數(shù)據(jù)初始化并未完成,像dataprops這些屬性無法訪問到
          • 到了created的時候,數(shù)據(jù)已經(jīng)初始化完成,能夠訪問dataprops這些屬性,但這時候并未完成dom的掛載,因此無法訪問到dom元素
          • 掛載方法是調(diào)用vm.$mount方法

          initState方法是完成props/data/method/watch/methods的初始化

          源碼位置:src\core\instance\state.js

          export function initState (vm: Component) {
            // 初始化組件的watcher列表
            vm._watchers = []
            const opts = vm.$options
            // 初始化props
            if (opts.props) initProps(vm, opts.props)
            // 初始化methods方法
            if (opts.methods) initMethods(vm, opts.methods)
            if (opts.data) {
              // 初始化data  
              initData(vm)
            } else {
              observe(vm._data = {}, true /* asRootData */)
            }
            if (opts.computed) initComputed(vm, opts.computed)
            if (opts.watch && opts.watch !== nativeWatch) {
              initWatch(vm, opts.watch)
            }
          }
          


          我們和這里主要看初始化data的方法為initData,它與initState在同一文件上

          function initData (vm: Component) {
            let data = vm.$options.data
            // 獲取到組件上的data
            data = vm._data = typeof data === 'function'
              ? getData(data, vm)
              : data || {}
            if (!isPlainObject(data)) {
              data = {}
              process.env.NODE_ENV !== 'production' && warn(
                'data functions should return an object:\n' +
                'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
                vm
              )
            }
            // proxy data on instance
            const keys = Object.keys(data)
            const props = vm.$options.props
            const methods = vm.$options.methods
            let i = keys.length
            while (i--) {
              const key = keys[i]
              if (process.env.NODE_ENV !== 'production') {
                // 屬性名不能與方法名重復(fù)
                if (methods && hasOwn(methods, key)) {
                  warn(
                    `Method "${key}" has already been defined as a data property.`,
                    vm
                  )
                }
              }
              // 屬性名不能與state名稱重復(fù)
              if (props && hasOwn(props, key)) {
                process.env.NODE_ENV !== 'production' && warn(
                  `The data property "${key}" is already declared as a prop. ` +
                  `Use prop default value instead.`,
                  vm
                )
              } else if (!isReserved(key)) { // 驗證key值的合法性
                // 將_data中的數(shù)據(jù)掛載到組件vm上,這樣就可以通過this.xxx訪問到組件上的數(shù)據(jù)
                proxy(vm, `_data`, key)
              }
            }
            // observe data
            // 響應(yīng)式監(jiān)聽data是數(shù)據(jù)的變化
            observe(data, true /* asRootData */)
          }
          


          仔細(xì)閱讀上面的代碼,我們可以得到以下結(jié)論:

          • 初始化順序:propsmethodsdata
          • data定義的時候可選擇函數(shù)形式或者對象形式(組件只能為函數(shù)形式)

          關(guān)于數(shù)據(jù)響應(yīng)式在這就不展開詳細(xì)說明

          上文提到掛載方法是調(diào)用vm.$mount方法

          源碼位置:

          Vue.prototype.$mount = function (
            el?: string | Element,
            hydrating?: boolean
          ): Component {
            // 獲取或查詢元素
            el = el && query(el)
          
            /* istanbul ignore if */
            // vue 不允許直接掛載到body或頁面文檔上
            if (el === document.body || el === document.documentElement) {
              process.env.NODE_ENV !== 'production' && warn(
                `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
              )
              return this
            }
          
            const options = this.$options
            // resolve template/el and convert to render function
            if (!options.render) {
              let template = options.template
              // 存在template模板,解析vue模板文件
              if (template) {
                if (typeof template === 'string') {
                  if (template.charAt(0) === '#') {
                    template = idToTemplate(template)
                    /* istanbul ignore if */
                    if (process.env.NODE_ENV !== 'production' && !template) {
                      warn(
                        `Template element not found or is empty: ${options.template}`,
                        this
                      )
                    }
                  }
                } else if (template.nodeType) {
                  template = template.innerHTML
                } else {
                  if (process.env.NODE_ENV !== 'production') {
                    warn('invalid template option:' + template, this)
                  }
                  return this
                }
              } else if (el) {
                // 通過選擇器獲取元素內(nèi)容
                template = getOuterHTML(el)
              }
              if (template) {
                /* istanbul ignore if */
                if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                  mark('compile')
                }
                /**
                 *  1.將temmplate解析ast tree
                 *  2.將ast tree轉(zhuǎn)換成render語法字符串
                 *  3.生成render方法
                 */
                const { render, staticRenderFns } = compileToFunctions(template, {
                  outputSourceRange: process.env.NODE_ENV !== 'production',
                  shouldDecodeNewlines,
                  shouldDecodeNewlinesForHref,
                  delimiters: options.delimiters,
                  comments: options.comments
                }, this)
                options.render = render
                options.staticRenderFns = staticRenderFns
          
                /* istanbul ignore if */
                if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                  mark('compile end')
                  measure(`vue ${this._name} compile`, 'compile', 'compile end')
                }
              }
            }
            return mount.call(this, el, hydrating)
          }
          


          閱讀上面代碼,我們能得到以下結(jié)論:

          • 不要將根元素放到body或者html
          • 可以在對象中定義template/render或者直接使用templateel表示元素選擇器
          • 最終都會解析成render函數(shù),調(diào)用compileToFunctions,會將template解析成render函數(shù)

          template的解析步驟大致分為以下幾步:

          • html文檔片段解析成ast描述符
          • ast描述符解析成字符串
          • 生成render函數(shù)

          生成render函數(shù),掛載到vm上后,會再次調(diào)用mount方法

          源碼位置:src\platforms\web\runtime\index.js

          // public mount method
          Vue.prototype.$mount = function (
            el?: string | Element,
            hydrating?: boolean
          ): Component {
            el = el && inBrowser ? query(el) : undefined
            // 渲染組件
            return mountComponent(this, el, hydrating)
          }
          


          調(diào)用mountComponent渲染組件

          export function mountComponent (
            vm: Component,
            el: ?Element,
            hydrating?: boolean
          ): Component {
            vm.$el = el
            // 如果沒有獲取解析的render函數(shù),則會拋出警告
            // render是解析模板文件生成的
            if (!vm.$options.render) {
              vm.$options.render = createEmptyVNode
              if (process.env.NODE_ENV !== 'production') {
                /* istanbul ignore if */
                if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
                  vm.$options.el || el) {
                  warn(
                    'You are using the runtime-only build of Vue where the template ' +
                    'compiler is not available. Either pre-compile the templates into ' +
                    'render functions, or use the compiler-included build.',
                    vm
                  )
                } else {
                  // 沒有獲取到vue的模板文件
                  warn(
                    'Failed to mount component: template or render function not defined.',
                    vm
                  )
                }
              }
            }
            // 執(zhí)行beforeMount鉤子
            callHook(vm, 'beforeMount')
          
            let updateComponent
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
              updateComponent = () => {
                const name = vm._name
                const id = vm._uid
                const startTag = `vue-perf-start:${id}`
                const endTag = `vue-perf-end:${id}`
          
                mark(startTag)
                const vnode = vm._render()
                mark(endTag)
                measure(`vue ${name} render`, startTag, endTag)
          
                mark(startTag)
                vm._update(vnode, hydrating)
                mark(endTag)
                measure(`vue ${name} patch`, startTag, endTag)
              }
            } else {
              // 定義更新函數(shù)
              updateComponent = () => {
                // 實(shí)際調(diào)?是在lifeCycleMixin中定義的_update和renderMixin中定義的_render
                vm._update(vm._render(), hydrating)
              }
            }
            // we set this to vm._watcher inside the watcher's constructor
            // since the watcher's initial patch may call $forceUpdate (e.g. inside child
            // component's mounted hook), which relies on vm._watcher being already defined
            // 監(jiān)聽當(dāng)前組件狀態(tài),當(dāng)有數(shù)據(jù)變化時,更新組件
            new Watcher(vm, updateComponent, noop, {
              before () {
                if (vm._isMounted && !vm._isDestroyed) {
                  // 數(shù)據(jù)更新引發(fā)的組件更新
                  callHook(vm, 'beforeUpdate')
                }
              }
            }, true /* isRenderWatcher */)
            hydrating = false
          
            // manually mounted instance, call mounted on self
            // mounted is called for render-created child components in its inserted hook
            if (vm.$vnode == null) {
              vm._isMounted = true
              callHook(vm, 'mounted')
            }
            return vm
          }
          


          閱讀上面代碼,我們得到以下結(jié)論:

          • 會觸發(fā)beforeCreate鉤子
          • 定義updateComponent渲染頁面視圖的方法
          • 監(jiān)聽組件數(shù)據(jù),一旦發(fā)生變化,觸發(fā)beforeUpdate生命鉤子

          updateComponent方法主要執(zhí)行在vue初始化時聲明的renderupdate方法

          render的作用主要是生成vnode

          源碼位置:src\core\instance\render.js

          // 定義vue 原型上的render方法
          Vue.prototype._render = function (): VNode {
              const vm: Component = this
              // render函數(shù)來自于組件的option
              const { render, _parentVnode } = vm.$options
          
              if (_parentVnode) {
                  vm.$scopedSlots = normalizeScopedSlots(
                      _parentVnode.data.scopedSlots,
                      vm.$slots,
                      vm.$scopedSlots
                  )
              }
          
              // set parent vnode. this allows render functions to have access
              // to the data on the placeholder node.
              vm.$vnode = _parentVnode
              // render self
              let vnode
              try {
                  // There's no need to maintain a stack because all render fns are called
                  // separately from one another. Nested component's render fns are called
                  // when parent component is patched.
                  currentRenderingInstance = vm
                  // 調(diào)用render方法,自己的獨(dú)特的render方法, 傳入createElement參數(shù),生成vNode
                  vnode = render.call(vm._renderProxy, vm.$createElement)
              } catch (e) {
                  handleError(e, vm, `render`)
                  // return error render result,
                  // or previous vnode to prevent render error causing blank component
                  /* istanbul ignore else */
                  if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
                      try {
                          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
                      } catch (e) {
                          handleError(e, vm, `renderError`)
                          vnode = vm._vnode
                      }
                  } else {
                      vnode = vm._vnode
                  }
              } finally {
                  currentRenderingInstance = null
              }
              // if the returned array contains only a single node, allow it
              if (Array.isArray(vnode) && vnode.length === 1) {
                  vnode = vnode[0]
              }
              // return empty vnode in case the render function errored out
              if (!(vnode instanceof VNode)) {
                  if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
                      warn(
                          'Multiple root nodes returned from render function. Render function ' +
                          'should return a single root node.',
                          vm
                      )
                  }
                  vnode = createEmptyVNode()
              }
              // set parent
              vnode.parent = _parentVnode
              return vnode
          }
          


          _update主要功能是調(diào)用patch,將vnode轉(zhuǎn)換為真實(shí)DOM,并且更新到頁面中

          源碼位置:src\core\instance\lifecycle.js

          Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
              const vm: Component = this
              const prevEl = vm.$el
              const prevVnode = vm._vnode
              // 設(shè)置當(dāng)前激活的作用域
              const restoreActiveInstance = setActiveInstance(vm)
              vm._vnode = vnode
              // Vue.prototype.__patch__ is injected in entry points
              // based on the rendering backend used.
              if (!prevVnode) {
                // initial render
                // 執(zhí)行具體的掛載邏輯
                vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
              } else {
                // updates
                vm.$el = vm.__patch__(prevVnode, vnode)
              }
              restoreActiveInstance()
              // update __vue__ reference
              if (prevEl) {
                prevEl.__vue__ = null
              }
              if (vm.$el) {
                vm.$el.__vue__ = vm
              }
              // if parent is an HOC, update its $el as well
              if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
                vm.$parent.$el = vm.$el
              }
              // updated hook is called by the scheduler to ensure that children are
              // updated in a parent's updated hook.
            }
          


          #三、結(jié)論

          • new Vue的時候調(diào)用會調(diào)用_init方法
            • 定義 $set$get$delete$watch 等方法
            • 定義 $on$off$emit$off等事件
            • 定義 _update$forceUpdate$destroy生命周期
          • 調(diào)用$mount進(jìn)行頁面的掛載
          • 掛載的時候主要是通過mountComponent方法
          • 定義updateComponent更新函數(shù)
          • 執(zhí)行render生成虛擬DOM
          • _update將虛擬DOM生成真實(shí)DOM結(jié)構(gòu),并且渲染到頁面中

          關(guān)注本頭條號,每天堅持更新原創(chuàng)干貨技術(shù)文章。

          如需學(xué)習(xí)視頻,請在微信搜索公眾號“智傳網(wǎng)優(yōu)”直接開始自助視頻學(xué)習(xí)

          1. 前言

          ExFAT(Extended File Allocation Table擴(kuò)展文件分配表)是一種針對SD卡和USB閃存驅(qū)動器(U盤)等閃存設(shè)備進(jìn)行優(yōu)化的Microsoft專有文件系統(tǒng)。

          它旨在取代舊的32位FAT32文件系統(tǒng),后者不能存儲大于4 GB的文件。

          最新版本的Windows和MacOS操作系統(tǒng)支持exFAT文件系統(tǒng)。

          CentOS與大多數(shù)其他主要Linux發(fā)行版一樣,默認(rèn)情況下不提供對專有exFAT文件系統(tǒng)的支持。

          如果您使用CentOS作為操作系統(tǒng),在嘗試掛載exFAT格式的USB驅(qū)動器時可能會遇到問題。

          本教程介紹如何在CentOS 7上啟用exFAT支持,以使CentOS7可以支持exFAT文件系統(tǒng),可以正常掛載exFAT的U盤。

          centos掛載exfat u盤

          2. CentOS系統(tǒng)怎么掛載exfat格式的U盤

          為了能夠在CentOS上安裝exFAT文件系統(tǒng),您需要安裝免費(fèi)的FUSE exFAT模塊和工具,為類Unix系統(tǒng)提供全功能的exFAT文件系統(tǒng)管理。

          在CentOS 7核心存儲庫中不提供exFAT軟件包。 您可以選擇從源構(gòu)建exFAT工具,或者使用Nux Dextop軟件倉庫中的yum進(jìn)行安裝。 我們將選擇第二種方案。

          Nux軟件倉庫依賴于EPEL軟件倉庫。 如果系統(tǒng)上未啟用EPEL存儲庫,請鍵入以下命令啟用它:

          sudo yum install epel-release
          

          接下來,導(dǎo)入yum倉庫GPG密鑰并通過安裝rpm軟件包啟用Nux存儲庫:

          sudo rpm -v --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
          sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
          

          啟用軟件倉庫后,使用以下命令安裝exfat-fuse和exfat-utils軟件包:

          sudo yum install exfat-utils fuse-exfat
          

          完成了!您現(xiàn)在可以安裝exFAT格式的設(shè)備。

          CentOS系統(tǒng)怎么掛載exfat格式的U盤

          3. 結(jié)論

          本文主要介紹了如何在CentOS 7計算機(jī)上啟用對exFAT文件系統(tǒng)的支持。 有些人將exFAT稱為FAT64。

          插入時USB驅(qū)動器將自動安裝,但如果自動安裝失敗,則必須手動掛載exFAT格式的U盤。

          centos7怎么掛載exfat格式U盤

          本文已同步至博客站,尊重原創(chuàng),轉(zhuǎn)載時請在正文中附帶以下鏈接:

          https://www.linuxrumen.com/rmxx/1224.html

          點(diǎn)擊了解更多,快速查看更多的技術(shù)文章列表。


          主站蜘蛛池模板: 性色AV一区二区三区无码| 精品一区二区三区四区在线| 四虎在线观看一区二区| 亚洲欧洲无码一区二区三区| 国产精品综合AV一区二区国产馆| 亚洲AV无码一区二区三区性色 | 国产午夜精品一区二区三区| 变态拳头交视频一区二区| 久久精品免费一区二区三区| 亚洲福利视频一区二区三区| 日韩欧美一区二区三区免费观看| 国产成人精品亚洲一区| 97精品国产福利一区二区三区| 国产精品第一区第27页| 果冻传媒董小宛一区二区| 国产电影一区二区| 视频一区二区三区免费观看| 日本在线一区二区| 99无码人妻一区二区三区免费| 国产伦精品一区二区三区在线观看| 冲田杏梨高清无一区二区| 国产精品视频一区二区三区无码| 亚洲精品色播一区二区| 中文字幕一区二区人妻| 一区二区亚洲精品精华液| 精品一区二区无码AV| 一区二区三区无码被窝影院| 精品成人一区二区三区四区| 无码国产精品一区二区免费式影视| jizz免费一区二区三区| 日本一区二区在线| 美女福利视频一区二区| 亚洲一区电影在线观看| 久久精品国产一区二区三区不卡| 在线观看日本亚洲一区| 无码乱人伦一区二区亚洲一| 秋霞午夜一区二区| 国产福利无码一区在线| 国产精品丝袜一区二区三区| 大帝AV在线一区二区三区| 久久se精品一区二区影院|