1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        服務(wù)端渲染SSR及實(shí)現(xiàn)原理

        共 10620字,需瀏覽 22分鐘

         ·

        2022-03-16 14:56

        本文首發(fā)于政采云前端團(tuán)隊(duì)博客:服務(wù)端渲染SSR及實(shí)現(xiàn)原理

        https://www.zoo.team/article/web-ssr

        前言

        在日常前端開發(fā)中,在需要首屏渲染速度優(yōu)化的場(chǎng)景下,大家或多或少都聽到過服務(wù)端渲染( SSR )。本文將結(jié)合 Vue 來對(duì) SSR 的實(shí)現(xiàn)邏輯來進(jìn)行解讀。通過閱讀本文你將了解到:

        • 服務(wù)端渲染的使用場(chǎng)景
        • Vue SSR 的實(shí)現(xiàn)原理
        • 可開箱即用的 SSR 腳手架

        服務(wù)端渲染

        服務(wù)端渲染 SSR (Server-Side Rendering),是指在服務(wù)端完成頁面的 html 拼接處理, 然后再發(fā)送給瀏覽器,將不具有交互能力的 html 結(jié)構(gòu)綁定事件和狀態(tài),在客戶端展示為具有完整交互能力的應(yīng)用程序。

        適用場(chǎng)景

        以下兩種情況 SSR 可以提供很好的場(chǎng)景支持

        • 需更好的支持 SEO

          優(yōu)勢(shì)在于同步。搜索引擎爬蟲是不會(huì)等待異步請(qǐng)求數(shù)據(jù)結(jié)束后再抓取信息的,如果 SEO 對(duì)應(yīng)用程序至關(guān)重要,但你的頁面又是異步請(qǐng)求數(shù)據(jù),那 SSR 可以幫助你很好的解決這個(gè)問題。

        • 需更快的到達(dá)時(shí)間

          優(yōu)勢(shì)在于慢網(wǎng)絡(luò)和運(yùn)行緩慢的設(shè)備場(chǎng)景。傳統(tǒng) SPA 需完整的 JS 下載完成才可執(zhí)行,而SSR 服務(wù)器渲染標(biāo)記在服務(wù)端渲染 html 后即可顯示,用戶會(huì)更快的看到首屏渲染頁面。如果首屏渲染時(shí)間轉(zhuǎn)化率對(duì)應(yīng)用程序至關(guān)重要,那可以使用 SSR 來優(yōu)化。

        不適用場(chǎng)景

        以下三種場(chǎng)景 SSR 使用需要慎重

        • 同構(gòu)資源的處理

          劣勢(shì)在于程序需要具有通用性。結(jié)合 Vue 的鉤子來說,能在 SSR 中調(diào)用的生命周期只有 beforeCreatecreated,這就導(dǎo)致在使用三方 API 時(shí)必須保證運(yùn)行不報(bào)錯(cuò)。在三方庫的引用時(shí)需要特殊處理使其支持服務(wù)端和客戶端都可運(yùn)行。

        • 部署構(gòu)建配置資源的支持

          劣勢(shì)在于運(yùn)行環(huán)境單一。程序需處于 node.js server 運(yùn)行環(huán)境。

        • 服務(wù)器更多的緩存準(zhǔn)備

          劣勢(shì)在于高流量場(chǎng)景需采用緩存策略。應(yīng)用代碼需在雙端運(yùn)行解析,cpu 性能消耗更大,負(fù)載均衡和多場(chǎng)景緩存處理比 SPA 做更多準(zhǔn)備。

        我們來結(jié)合 Vue.js 來看看 Vue 是如何實(shí)現(xiàn) SSR 的。

        Vue SSR 的實(shí)現(xiàn)原理

        先決條件

        組件基于 Vnode 來實(shí)現(xiàn)渲染

        VNode 本身是 js 對(duì)象,兼容性極強(qiáng),不依賴當(dāng)前的執(zhí)行的環(huán)境,從而可以在服務(wù)端渲染及原生渲染。虛擬 DOM 頻繁修改,最后比較出真實(shí) DOM 需要更改的地方,可以達(dá)到局部渲染的目的,減少性能損耗。

        vue-server-renderer

        是一個(gè)具有獨(dú)立渲染應(yīng)用程序能力的包,是 Vue 服務(wù)端渲染的核心代碼。

        本文下面的源碼也結(jié)合這個(gè)包展開,此處不多冗余介紹。

        SSR 渲染架構(gòu)

        我們結(jié)合官網(wǎng)圖項(xiàng)目架構(gòu)兩個(gè)維度來整體了解一下 SSR 全貌

        項(xiàng)目架構(gòu)

        src
        ├──?components
        ├──?App.vue
        ├──?app.js?----通用?entry
        ├──?entry-client.js?----僅運(yùn)行于瀏覽器
        └──?entry-server.js?----僅運(yùn)行于服務(wù)器

        app.js導(dǎo)出 createApp 函數(shù)工廠,此函數(shù)是可以被重復(fù)執(zhí)行的,從根 Vue 實(shí)例注入,用于創(chuàng)建 router,store 以及應(yīng)用程序?qū)嵗?/p>

        import?Vue?from?'vue'
        import?App?from?'./App.vue'
        //?導(dǎo)出一個(gè)工廠函數(shù),用于創(chuàng)建新的應(yīng)用程序、router?和?store?實(shí)例
        export?function?createApp?()?{
        ??const?app?=?new?Vue({
        ????render:?h?=>?h(App)
        ??})
        ??return?{?app?}
        }

        entry-client.js負(fù)責(zé)創(chuàng)建應(yīng)用程序,掛載實(shí)例 DOM ,僅運(yùn)行于瀏覽器。

        import?{?createApp?}?from?'./app'
        const?{?app?}?=?createApp()
        //?#app?為根元素,名稱可替換
        app.$mount('#app')

        entry-server.js創(chuàng)建返回應(yīng)用實(shí)例,同時(shí)還會(huì)進(jìn)行路由匹配和數(shù)據(jù)的預(yù)處理,僅運(yùn)行于服務(wù)器。

        import?{?createApp?}?from?'./app'
        export?default?context?=>?{
        ??const?{?app?}?=?createApp()
        ??return?app
        }

        服務(wù)端和客戶端代碼編寫原則

        作為同構(gòu)框架,應(yīng)用代碼編譯過程 Vue SSR 提供了兩個(gè)編譯入口,來作為抹平由于環(huán)境不同的代碼差異。Client entry 和 Server entry 中編寫代碼邏輯的區(qū)分有兩條原則

        1. 通用型代碼 可通用性的代碼,由于鑒權(quán)邏輯和網(wǎng)關(guān)配置不同,需要在 webpack resolve.alias 中配置不同的模塊環(huán)境應(yīng)用。

        2. 非通用性代碼 Client entry ?負(fù)責(zé)掛載 DOM 節(jié)點(diǎn)代碼,以及三方包引入和具有兼容性庫的加載。

        Server entry 只生成 Vue 對(duì)象。

        兩個(gè)編譯產(chǎn)物

        經(jīng)過 webpack 打包之后會(huì)有兩個(gè) bundle 產(chǎn)物

        server bundle 用于生成 vue-ssr-server-bundle.json,我們熟悉的 sourceMap 和需要在服務(wù)端運(yùn)行的代碼列表都在這個(gè)產(chǎn)物中。

        vue-SSR-server-bundle.json
        {?
        ??"entry":?,?
        ??"files":?{
        ??? A:包含了所有要在服務(wù)端運(yùn)行的代碼列表
        ??? B:入口文件
        ??}?
        }

        client Bundle 用于生成 vue-SSR-client-manifest.json,包含所有的靜態(tài)資源,首次渲染需要加載的 script 標(biāo)簽,以及需要在客戶端運(yùn)行的代碼。

        vue-SSR-client-manifest.json
        {?
        ??"publicPath":?公共資源路徑文件地址,?
        ??"all":?資源列表
        ??"initial":輸出?html?字符串
        ??"async":?異步加載組件集合
        ??"modules":?moduleIdentifier?和?all?數(shù)組中文件的映射關(guān)系
        }

        先決條件中我們提到了一個(gè)重要的包 vue-server-renderer,那我們來重點(diǎn)看看這個(gè)包里面的值得我們學(xué)習(xí)關(guān)注的內(nèi)容。

        vue-server-renderer

        是 Vue SSR 的核心代碼,值得我們關(guān)注的是應(yīng)用初始化應(yīng)用輸出。兩個(gè)階段提供了完整的應(yīng)用層代碼編譯和組裝邏輯。

        應(yīng)用初始化

        在應(yīng)用初始化過程中,重點(diǎn)展開介紹實(shí)例化流程防止交叉污染。

        首先我們先來看看一個(gè) Vue SSR 的應(yīng)用是如何被初始化的。

        實(shí)例化流程

        1. 生成 Vue 對(duì)象
        const?Vue?=?require('vue')
        const?app?=?new?Vue()
        1. 生成 renderer,值得關(guān)注的兩個(gè)對(duì)象 render 和 templateRenderer
        const?renderer?=?require('vue-server-renderer').createRenderer()
        // createRenderer 函數(shù)中有兩個(gè)重要的對(duì)象:render 和 templateRenderer
        function?createRenderer?(ref)?{
        ??//?render:?渲染?html?組件
        ??var?render?=?createRenderFunction(modules,?directives,?isUnaryTag,?cache);
        ??//?templateRenderer:?模版渲染,clientManifest?文件
        ??var?templateRenderer?=?new?TemplateRenderer({
        ????template:?template,
        ????inject:?inject,
        ????shouldPreload:?shouldPreload,
        ????shouldPrefetch:?shouldPrefetch,
        ????clientManifest:?clientManifest,
        ????serializer:?serializer
        ??});

        經(jīng)過這個(gè)過程的 render 和 templateRenderer 并沒有被調(diào)用,這兩個(gè)函數(shù)真正的調(diào)用是在項(xiàng)目實(shí)例化 createBundleRenderer 函數(shù)的時(shí)候,即第三步創(chuàng)建的函數(shù)。

        1. 創(chuàng)建沙盒 vm,實(shí)例化 Vue 的入口文件
        var?vm?=?require('vm');
        //?調(diào)用?createBundleRunner?函數(shù)實(shí)例對(duì)象,rendererOptions?支持可配置
        var?run?=?createBundleRunner(?
        ??entry,?----入口文件集合
        ??files,?----打包文件集合
        ??basedir,?
        ? rendererOptions.runInNewContext。
        );}

        在 createBundleRunner 方法的源碼到其實(shí)舉例了一個(gè)叫 compileModule 的一個(gè)方法,這個(gè)方法中有兩個(gè)函數(shù):getCompiledScriptevaluateModule

        function?createBundleRunner?(entry,?files,?basedir,?runInNewContext)?{
        ??//觸發(fā)?compileModule?方法,找到?webpack?編譯形成的?code
        ??var?evaluate?=?compileModule(files,?basedir,?runInNewContext);
        }

        getCompiledScript:編譯 wrapper ,找到入口文件的 files 文件名及 script 腳本的編譯執(zhí)行

        function?getCompiledScript?(filename)?{
        ????if?(compiledScripts[filename])?{
        ??????return?compiledScripts[filename]
        ????}
        ????//?在入口文件?files?中找到對(duì)應(yīng)的文件名稱
        ????var?code?=?files[filename];
        ????var?wrapper?=?NativeModule.wrap(code);
        ????//?在沙盒上下文中執(zhí)行構(gòu)建?script?腳本
        ????var?script?=?new?vm.Script(wrapper,?{
        ??????filename:?filename,
        ??????displayErrors:?true
        ????});
        ????compiledScripts[filename]?=?script;
        ????return?script
        ??}

        evaluateModule:根據(jù) runInThisContext 中的配置項(xiàng)來決定是在當(dāng)前上下文執(zhí)行還是單獨(dú)上下文執(zhí)行。

        function?evaluateModule?(filename,?sandbox,?evaluatedFiles)?{
        ????if?(?evaluatedFiles?===?void?0?)?evaluatedFiles?=?{};
        ????if?(evaluatedFiles[filename])?{
        ??????return?evaluatedFiles[filename]
        ????}
        ????var?script?=?getCompiledScript(filename);
        ????//?用于判斷是在當(dāng)前的那種模式下面執(zhí)行沙盒上下文,此時(shí)存在兩個(gè)函數(shù)的相互調(diào)用
        ????var?compiledWrapper?=?runInNewContext?===?false
        ????????script.runInThisContext()
        ??????:?script.runInNewContext(sandbox);
        ????//?m:?函數(shù)導(dǎo)出的?exports?數(shù)據(jù)
        ????var?m?=?{?exports:?{}};
        ????//?r:?替代原生?require?用來解析?bundle?中通過?require?函數(shù)引用的模塊
        ????var?r?=?function?(file)?{
        ??????...
        ??????return?require(file)
        ????};
        ???}

        上述的函數(shù)執(zhí)行完成之后會(huì)調(diào)用 compiledWrapper.call,傳參對(duì)應(yīng)上面的 exports、require、module, 我們就能拿到入口函數(shù)。

        1. 錯(cuò)誤拋出容錯(cuò)和全局錯(cuò)誤監(jiān)聽 renderToString: 在沒有 cb 函數(shù)時(shí)做了 promise 的返回,那說明我們?cè)谡{(diào)用次函數(shù)的時(shí)候可以直接做 try catch的處理,用于全局錯(cuò)誤的拋出容錯(cuò)。
        renderToString:?function?(context,?cb)?{
        ????var?assign;
        ????if?(typeof?context?===?'function')?{
        ??????cb?=?context;
        ??????context?=?{};
        ????}
        ????var?promise;
        ????if?(!cb)?{
        ??????((assign?=?createPromiseCallback(),?promise?=?assign.promise,?cb?=?assign.cb));
        ????}
        ????...
        ????return?promise
        ??},
        }

        renderToStream:對(duì)拋錯(cuò)做了監(jiān)聽機(jī)制, 拋錯(cuò)的鉤子函數(shù)將在這個(gè)方法中觸發(fā)。

        ?renderToStream:?function?(context)?{
        ????var?res?=?new?PassThrough();
        ????run(context).catch(function?(err)?{
        ??????rewriteErrorTrace(err,?maps);
        ??????//?此處做了監(jiān)聽器的容錯(cuò)
        ??????process.nextTick(function?()?{
        ????????res.emit('error',?err);
        ??????});
        ????}).then(function?(app)?{
        ??????if?(app)?{
        ????????var?renderStream?=?renderer.renderToStream(app,?context);
        ????????...?
        ??????}
        ????}
        ?}

        防止交叉污染

        Node.js 服務(wù)器是一個(gè)長期運(yùn)行的進(jìn)程,在客戶端編寫的代碼在進(jìn)入進(jìn)程時(shí),變量的上下文將會(huì)被保留,導(dǎo)致交叉請(qǐng)求狀態(tài)污染。因此不可共享一個(gè)實(shí)例,所以說 createApp 是一個(gè)可被重復(fù)執(zhí)行的函數(shù)。其實(shí)在包內(nèi)部,變量之間也存在防止交叉污染的能力。

        防止交叉污染的能力是由 rendererOptions.runInNewContext 這個(gè)配置項(xiàng)來提供的,這個(gè)配置支持 true, false,和 once 三種配置項(xiàng)傳入。

        //?rendererOptions.runInNewContext?可配置項(xiàng)如下
        ??true:?
        ??新上下文模式:創(chuàng)建新上下文并重新評(píng)估捆綁包在每個(gè)渲染上。
        ??確保每個(gè)應(yīng)用程序的整個(gè)應(yīng)用程序狀態(tài)都是新的渲染,但會(huì)產(chǎn)生額外的評(píng)估成本。
        ??false:
        ??直接模式:
        ??每次渲染時(shí),它只調(diào)用導(dǎo)出的函數(shù)。而不是在上重新評(píng)估整個(gè)捆綁包
        ??模塊評(píng)估成本較高,但需要結(jié)構(gòu)化源代碼
        ??once:?
        ??初始上下文模式
        ??僅用于收集可能的非組件 vue 樣式加載程序注入的樣式。

        特別說明一下 false 和 once 的場(chǎng)景, 為了防止交叉污染,在渲染的過程中對(duì)作用域要求很嚴(yán)格,以此來保證在不同的對(duì)象彼此之間不會(huì)形成污染。

        if?(!runner)?{
        ???var?sandbox?=?runInNewContext?===?'once'
        ????????createSandbox()
        ??????:?global;
        ????initialContext?=?sandbox.__VUE_SSR_CONTEXT__?=?{};
        ????runner?=?evaluate(entry,?sandbox);
        ????//在后續(xù)渲染中,_VUE_SSR_CONTEXT_uu?將不可用
        ????//防止交叉污染
        ????delete?sandbox.__VUE_SSR_CONTEXT__;
        ????if?(typeof?runner?!==?'function')?{
        ??????throw?new?Error(
        ????????'bundle?export?should?be?a?function?when?using?'?+
        ????????'{?runInNewContext:?false?}.'
        ??????)
        ????}
        ??}

        應(yīng)用輸出

        在應(yīng)用輸出這個(gè)階段中,SSR 將更多側(cè)重加載腳本內(nèi)容模版渲染,在模版渲染時(shí)在代碼中是否定義過模版引擎源碼將提供不同的 html 拼接結(jié)構(gòu)。

        加載腳本內(nèi)容

        此過程會(huì)將上個(gè)階段構(gòu)造的 reader 和 templateRender 方法實(shí)現(xiàn)數(shù)據(jù)綁定。

        templateRenderer:負(fù)責(zé) html 封裝,其原型上會(huì)有如下幾個(gè)方法, 這些函數(shù)的作用如下圖。值得一提的是:bindRenderFns 函數(shù)是將 4 個(gè) render 函數(shù)綁定到用戶上下文的 context 中,用戶在拿到這些內(nèi)容之后就可以做內(nèi)容的自定義組裝和渲染。

        render: 函數(shù)會(huì)被遞歸調(diào)用按照從父到子的順序,將組件全部轉(zhuǎn)化為 html。

        function?createRenderFunction?(
        ??modules,
        ??directives,
        ??isUnaryTag,
        ??cache
        )?
        {
        ??return?function?render?(
        ????component,
        ????write,
        ????userContext,
        ????done
        ??
        )?
        {
        ????warned?=?Object.create(null);
        ????var?context?=?new?RenderContext({
        ??????activeInstance:?component,
        ??????userContext:?userContext,
        ??????write:?write,?done:?done,?renderNode:?renderNode,
        ??????isUnaryTag:?isUnaryTag,?modules:?modules,?directives:?directives,
        ??????cache:?cache
        ????});
        ????installSSRHelpers(component);
        ????normalizeRender(component);
        ????//?渲染?node?節(jié)點(diǎn),綁定用戶作用上下文
        ????var?resolve?=?function?()?{
        ??????renderNode(component._render(),?true,?context);
        ????};
        ????//?等待組件?serverPrefetch?執(zhí)行完成之后,_render?生成子節(jié)點(diǎn)的?vnode?進(jìn)行渲染
        ????waitForServerPrefetch(component,?resolve,?done);
        ??}
        }

        在經(jīng)過上面的編譯流程之后,我們已經(jīng)拿到了 html 字符串,但如果要在瀏覽器中展示頁面還需js, css 等標(biāo)簽與這個(gè) html 組裝成一個(gè)完整的報(bào)文輸出到瀏覽器中, 因此需要模版渲染階段來將這些元素實(shí)現(xiàn)組裝。

        模版渲染

        經(jīng)過應(yīng)用初始化階段,代碼被編譯獲取了 html 字符串,context 渲染需要依賴的 templateRenderer.prototype.bindRenderFns 中綁定的 state, script , styles 等資源。

        TemplateRenderer.prototype.bindRenderFns?=?function?bindRenderFns?(context)?{
        ??var?renderer?=?this
        ??;['ResourceHints',?'State',?'Scripts',?'Styles'].forEach(function?(type)?{
        ????context[("render"?+?type)]?=?renderer[("render"?+?type)].bind(renderer,?context);
        ??});
        ? context.getPreloadFiles = r**erer.ge****:**reloadFiles.bind(renderer, context);
        };

        在具體渲染模版時(shí),會(huì)有以下兩種情況:

        • 未定義模版引擎 渲染結(jié)果會(huì)被直接返回給 renderToString 的回調(diào)函數(shù),而頁面所需要的腳本依賴我們通過用戶上下文 context 的 renderStyles,renderResourceHints、renderState、renderScripts 這些函數(shù)分別獲得。

        • 定義了模版引擎 templateRender 會(huì)幫助我們進(jìn)行 html 組裝

        TemplateRenderer.prototype.render?=?function?render?(content,?context)?{
        //?parsedTemplate?用于解析函數(shù)得到的包含三個(gè)部分的?compile?對(duì)象,
        //?按照順序進(jìn)行字符串模版的拼接
        ??var?template?=?this.parsedTemplate;
        ??if?(!template)?{
        ????throw?new?Error('render?cannot?be?called?without?a?template.')
        ??}
        ??context?=?context?||?{};
        ?
        ??if?(typeof?template?===?'function')?{
        ????return?template(content,?context)
        ??}
        ?
        ??if?(this.inject)?{
        ????return?(
        ??????template.head(context)?+
        ??????(context.head?||?'')?+
        ??????this.renderResourceHints(context)?+
        ??????this.renderStyles(context)?+
        ??????template.neck(context)?+
        ??????content?+
        ??????this.renderState(context)?+
        ??????this.renderScripts(context)?+
        ??????template.tail(context)
        ????)
        ??}?else?{
        ????...
        ??}
        };

        至此我們了解了 Vue SSR 的整體架構(gòu)邏輯和 vue-server-renderer 的核心代碼,當(dāng)然 SSR 也是有很多開箱即用的腳手架來供我們選擇的。

        開箱即用的SSR腳手架

        目前前端流行的三種技術(shù)棧 React, Vue 和 Angula,已經(jīng)孵化出對(duì)應(yīng)的服務(wù)端渲染框架,開箱即用,感興趣的同學(xué)可以自主學(xué)習(xí)使用。

        • React: Next.js
        • Vue: ?Nuxt.js
        • Angula: Nest.js

        總結(jié)

        服務(wù)端渲染 ( SSR ) 是一個(gè)同構(gòu)程序,是否使用 SSR 取決于內(nèi)容到達(dá)時(shí)間對(duì)應(yīng)用程序的重要程度。如果對(duì)初始加載的幾百毫秒可接受,SSR 的使用就有點(diǎn)小題大做了。

        對(duì)于源碼的學(xué)習(xí)可以幫助更好借鑒優(yōu)秀的程序?qū)懛ê图ぐl(fā)對(duì)日常代碼編程架構(gòu)的思考,如果你更傾向箱即用的解決方案,那可以使用現(xiàn)有的 SSR 腳手架來搭建項(xiàng)目,這些腳手架的模版抽象和額外的功能擴(kuò)展可以提供平滑的開箱體驗(yàn)。

        參考文獻(xiàn)

        • Vue SSR 官網(wǎng) (https://ssr.vuejs.org/zh)
        • Vue 使用指南 (https://www.w3cschool.cn/vuessr/vuessr-jep83epx.html)
        • Vue SSR 源碼解析 (https://juejin.cn/post/6844903812700831757)

        看完兩件事

        如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我兩件小事

        1.點(diǎn)個(gè)「在看」,讓更多人也能看到這篇內(nèi)容(點(diǎn)了在看」,bug -1 ??

        2.關(guān)注公眾號(hào)「全棧前端精選」,持續(xù)為你推送精選好文

        招賢納士

        政采云前端團(tuán)隊(duì)(ZooTeam),一個(gè)年輕富有激情和創(chuàng)造力的前端團(tuán)隊(duì),隸屬于政采云產(chǎn)品研發(fā)部,Base 在風(fēng)景如畫的杭州。團(tuán)隊(duì)現(xiàn)有 50 余個(gè)前端小伙伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風(fēng)暴團(tuán)。成員構(gòu)成既有來自于阿里、網(wǎng)易的“老”兵,也有浙大、中科大、杭電等校的應(yīng)屆新人。團(tuán)隊(duì)在日常的業(yè)務(wù)對(duì)接之外,還在物料體系、工程平臺(tái)、搭建平臺(tái)、性能體驗(yàn)、云端應(yīng)用、數(shù)據(jù)分析及可視化等方向進(jìn)行技術(shù)探索和實(shí)戰(zhàn),推動(dòng)并落地了一系列的內(nèi)部技術(shù)產(chǎn)品,持續(xù)探索前端技術(shù)體系的新邊界。

        如果你想改變一直被事折騰,希望開始能折騰事;如果你想改變一直被告誡需要多些想法,卻無從破局;如果你想改變你有能力去做成那個(gè)結(jié)果,卻不需要你;如果你想改變你想做成的事需要一個(gè)團(tuán)隊(duì)去支撐,但沒你帶人的位置;如果你想改變既定的節(jié)奏,將會(huì)是“5 年工作時(shí)間 3 年工作經(jīng)驗(yàn)”;如果你想改變本來悟性不錯(cuò),但總是有那一層窗戶紙的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望參與到隨著業(yè)務(wù)騰飛的過程,親手推動(dòng)一個(gè)有著深入的業(yè)務(wù)理解、完善的技術(shù)體系、技術(shù)創(chuàng)造價(jià)值、影響力外溢的前端團(tuán)隊(duì)的成長歷程,我覺得我們?cè)摿牧?。任何時(shí)間,等著你寫點(diǎn)什么,發(fā)給 [email protected]


        瀏覽 56
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日本无人区乱码1区2区3区 | 巨大阳根挺进美女稚嫩花 | 在线免费观看无码 | 吴梦梦AV无码一区二区三区小说 | eyan一181北野未奈授乳中 | 99爱免费视频 | 大香蕉太香蕉成人现现 | 黄色录像第一集 | 日产精品一线二线三线芒果 | 婷婷五月在线视频 |