国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Vue同構(gòu)(三): 狀態(tài)與數(shù)據(jù)

韓冰 / 1169人閱讀

摘要:最與眾不同的是,該組件我們對(duì)外暴露了一個(gè)自定義的靜態(tài)函數(shù),因?yàn)槭墙M件的靜態(tài)函數(shù),因此我們可以在組件都沒(méi)創(chuàng)建實(shí)例之前就調(diào)用方法,但是因?yàn)檫€未創(chuàng)建實(shí)例,因此函數(shù)內(nèi)部不能訪問(wèn)。上篇文章是在回調(diào)函數(shù)中直接執(zhí)行了將對(duì)應(yīng)的組件實(shí)例傳遞。

前言

  首先歡迎大家關(guān)注我的Github博客,也算是對(duì)我的一點(diǎn)鼓勵(lì),畢竟寫(xiě)東西沒(méi)法變現(xiàn),堅(jiān)持下去也是靠的是自己的熱情和大家的鼓勵(lì)。各位讀者的Star是激勵(lì)我前進(jìn)的動(dòng)力,請(qǐng)不要吝惜。

  Vue同構(gòu)系列的文章已經(jīng)出到第三篇了,前兩篇文章Vue同構(gòu)(一): 快速上手與Vue同構(gòu)(二):路由與代碼分割都取得了不錯(cuò)的反響(可能是錯(cuò)覺(jué)),前兩篇文章本質(zhì)上講了如何在服務(wù)端渲染中使用Vue與Vue Router,基本的Vue全家桶中除了Vuex還沒(méi)有講,這篇文章也是圍繞這個(gè)主題來(lái)講的。

引子

  一直很認(rèn)同Redux作者Dan Abramov的一句話:

Flux 架構(gòu)就像眼鏡:你自會(huì)知道什么時(shí)候需要它。

  其中頗有幾分“只可意會(huì)不可言傳”的感覺(jué),我們先來(lái)看看什么情況下我們需要在服務(wù)端渲染中引入Vuex?

  前面的兩篇文章的例子都足夠的簡(jiǎn)單,然而實(shí)際的業(yè)務(wù)場(chǎng)景并不會(huì)如此的簡(jiǎn)單。比如我們想要渲染的是文章的列表,那我們肯定需要向數(shù)據(jù)源請(qǐng)求數(shù)據(jù)。在客戶端渲染中,這一切太稀疏平常了。你可能馬上會(huì)想到在組件的生命周期mounted方法中去請(qǐng)求異步的數(shù)據(jù)接口,然后將請(qǐng)求的數(shù)據(jù)賦值給Vue的響應(yīng)式數(shù)據(jù),Vue會(huì)自動(dòng)刷新界面,一切都是如此的完美,比如像下面的例子:


  但是到了服務(wù)器渲染中,你想這么干是鐵定行不通了,因?yàn)樵诜?wù)端壓根就不會(huì)執(zhí)行到mounted的生命周期中,我們之前說(shuō)過(guò)在服務(wù)器端Vue的實(shí)例僅僅只會(huì)執(zhí)行生命周期函數(shù)beforeCreatecreated,那么我們把數(shù)據(jù)請(qǐng)求的邏輯放置在這個(gè)兩個(gè)生命周期中是否可行呢?答案是不可以的,因?yàn)閿?shù)據(jù)請(qǐng)求的操作是異步的,我們并不能預(yù)期什么時(shí)候數(shù)據(jù)能返回。并且我們還需要考慮到,不僅服務(wù)端在渲染界面的時(shí)候需要數(shù)據(jù),客戶端也需要首屏頁(yè)面的數(shù)據(jù),因?yàn)榭蛻舳诵枰獙?duì)其進(jìn)行激活,難道我們需要分別在服務(wù)端和服務(wù)端兩次請(qǐng)求同一份數(shù)據(jù)嗎?那么無(wú)論是服務(wù)器還是數(shù)據(jù)源都會(huì)壓力陡增,肯定不是我們所希望看到的。

  其實(shí)解決方案還是比較明確的:數(shù)據(jù)和組件分離,我們?cè)诜?wù)器渲染組件之前就將數(shù)據(jù)準(zhǔn)備好并放置在容器中,因此服務(wù)器渲染的過(guò)程中就可以直接從容器中拿現(xiàn)成的數(shù)據(jù)渲染。不僅如此,我們可以將該容器中的數(shù)據(jù)直接序列化,注入到請(qǐng)求的HTML中,這樣客戶端激活組件的時(shí)候,也能直接拿到相同的數(shù)據(jù)進(jìn)行渲染,不僅僅能減少相同的數(shù)據(jù)的請(qǐng)求并且還可以防止因?yàn)檎?qǐng)求數(shù)據(jù)的不相同導(dǎo)致的激活失敗從而客戶端重新渲染(開(kāi)發(fā)模式下,生產(chǎn)模式下不會(huì)檢測(cè),則激活就會(huì)出錯(cuò))。那誰(shuí)來(lái)?yè)?dān)任數(shù)據(jù)容器的職責(zé)呢,顯然就是我們今天講的Vuex了。

服務(wù)端數(shù)據(jù)預(yù)取

  我們接著在上一篇文章中代碼的構(gòu)建配置基礎(chǔ)上開(kāi)始我們的嘗試(文末會(huì)有代碼鏈接),首先我們來(lái)說(shuō)說(shuō)我們目標(biāo),我們借用CNode提供的文章接口,然后在界面中渲染出不同標(biāo)簽下的文章列表,不同路由標(biāo)簽之間切換可以加載不同的文章列表。我們使用axios作為Node服務(wù)端和瀏覽器客戶端通用的HTTP請(qǐng)求庫(kù)。先寫(xiě)接口, CNode給我們提供了如下的接口:

GET 
URL: https://cnodejs.org/api/v1/to...

參數(shù): page Number 頁(yè)數(shù)
參數(shù): tab 主題分類。目前有 ask share job good
參數(shù): limit Number 每一頁(yè)的主題數(shù)量

  我們這次就選三個(gè)tab主題分別使用,分別是精華(good)、分享(share)、問(wèn)答(ask)

  首先對(duì)組件提供接口:

// api/index.js
import axios from "axios";

export function fetchList(tab = "good") {
    const url = `https://cnodejs.org/api/v1/topics?limit=20&tab=${tab}`;
    return axios.get(url).then((data)=>{
        return data.data;
    })
}

  作為演示我們僅渲染前20條數(shù)據(jù)。

  接下來(lái)我們引入Vuex,之前兩篇文章都提到了我們需要為每次請(qǐng)求都生成新的Vue與Vue Router實(shí)例,其根本原因是防止不同請(qǐng)求之間數(shù)據(jù)共享導(dǎo)致的狀態(tài)污染。Vuex也是相同的原因,我們需要為每次請(qǐng)求都生成新的Vuex實(shí)例。

import Vue from "vue"
import Vuex from "vuex"

import { fetchList } from "../api"

Vue.use(Vuex)

export function createStore() {
    return new Vuex.Store({
        state: {
            good: [],
            ask: [],
            share: []
        },

        actions: {
            fetchItems: function ({commit}, key = "good") {
                return fetchList(key).then( res => {
                    if(res.success){
                        commit("addItems", {
                            key,
                            items: res.data
                        })
                    }
                })
            }
        },

        mutations: {
            addItems: function (state, payload) {
                const {key, items} = payload;
                state[key].push(...items);
            }
        }
    })
}

  這里我們假設(shè)你已經(jīng)對(duì)Vuex有所了解,首先我們調(diào)用Vue.use(Vuex)將Vuex注入到Vue中,然后每次調(diào)用createStore都會(huì)返回新的Vuex實(shí)例,其中state中包含goodaskshare數(shù)組用來(lái)存儲(chǔ)對(duì)應(yīng)主題的文章信息。 名為addItemsmutation負(fù)責(zé)向state中對(duì)應(yīng)的數(shù)組中增加數(shù)據(jù),而名為fetchItemsaction則負(fù)責(zé)調(diào)用異步接口請(qǐng)求數(shù)據(jù)并更新對(duì)應(yīng)的mutation

  那我們什么時(shí)候調(diào)用fetchItems是需要考慮一下。特定路由對(duì)應(yīng)于特定的組件,而特定的組件則需要特定數(shù)據(jù)做渲染。我們說(shuō)過(guò)的實(shí)現(xiàn)邏輯是在組件渲染前就獲取到所用的數(shù)據(jù),在純客戶端渲染的程序中我們將請(qǐng)求的邏輯放置在對(duì)應(yīng)組件的生命周期中,在服務(wù)端渲染中,我們?nèi)匀粚⒃撨壿嫹胖迷诮M件內(nèi),這樣,不僅在服務(wù)端渲染的時(shí)候通過(guò)匹配的組件就能執(zhí)行其請(qǐng)求數(shù)據(jù)的邏輯,并且在客戶端激活后,組件內(nèi)部也可以在必要的時(shí)刻中執(zhí)行邏輯去請(qǐng)求或者更新數(shù)據(jù)。我們看例子:

// TopicList.vue




  Vue組件的模板不需要解釋,之所以增加button按鈕來(lái)打開(kāi)對(duì)應(yīng)文章的鏈接主要是想驗(yàn)證客戶端是否正確激活。該組件從store中獲取數(shù)據(jù),其中routeid表示文章的主題。最與眾不同的是,該組件我們對(duì)外暴露了一個(gè)自定義的靜態(tài)函數(shù)asyncData,因?yàn)槭墙M件的靜態(tài)函數(shù),因此我們可以在組件都沒(méi)創(chuàng)建實(shí)例之前就調(diào)用方法,但是因?yàn)檫€未創(chuàng)建實(shí)例,因此函數(shù)內(nèi)部不能訪問(wèn)thisasyncData內(nèi)部邏輯是觸發(fā)store中的fetchItemsaction

  接下來(lái)我們看路由的配置:

import Vue from "vue"
import Router from "vue-router"

Vue.use(Router)

export function createRouter() {
    return new Router({
        mode: "history",
        routes: [{
            path: "/good",
            component: () => import("../components/TopicListCopy.vue")
        },{
            path: "/:id",
            component: () => import("../components/TopicList.vue")
        }]
    })
}

  我們給good路由配置了特殊的TopicListCopy組件,他與TopicList除了名字之外,其他的全部一樣,其他的路由我們使用前面介紹的TopicList組件,之所以要這么做主要是出于方便后面介紹其中的操作。

  然后我們看一下應(yīng)用的入口app.js:

import Vue from "vue"

import { createStore } from "./store"
import { createRouter } from "./router"

import App from "./components/App.vue"

export function createApp() {

    const store = createStore()
    const router = createRouter()
    
    const app =  new Vue({
        store,
        router,
        render: h => h(App)
    })

    return {
        app,
        store,
        router
    }
}

  和之前的代碼大致相同,只不過(guò)在每次調(diào)用createApp函數(shù)的時(shí)候,創(chuàng)建Vuex的實(shí)例store,并給Vue實(shí)例注入store實(shí)例。

  接下來(lái)看服務(wù)端渲染的入口entry-server.js:

// entry-server.js
import { createApp } from "./app"

export default function (context) {
    return new Promise((resolve, reject) => {
        const {app, store, router} = createApp()
        router.push(context.url)
        router.onReady(() => {
            const matchedComponents = router.getMatchedComponents()
            if(matchedComponents.length <= 0){
                return reject({ code: 404 })
            }else {
                Promise.all(matchedComponents.map((component) => {
                    if(component.asyncData){
                    
                        return component.asyncData({
                            store,
                            route: router.currentRoute
                        })
                    }
                })).then(()=> {
                    context.state = store.state
                    resolve(app)
                })
            }
        }, reject)
    })
}

  服務(wù)端的渲染入口文件和之前的結(jié)構(gòu)基本保持一致,onReady會(huì)在所有的異步鉤子函數(shù)異步組件加載完畢之后執(zhí)行傳遞的回調(diào)函數(shù)。上篇文章是在onReady回調(diào)函數(shù)中直接執(zhí)行了resolve(app)將對(duì)應(yīng)的組件實(shí)例傳遞。但是在這里我們做了一些其他的工作。首先我們調(diào)用了router.getMatchedComponents()獲取了當(dāng)前路由匹配的路由組件,注意我們這里匹配的路由組件并不是實(shí)例而僅僅只是配置對(duì)象,然后我們調(diào)用所有匹配的路由組件中的asyncData靜態(tài)方法,加載各個(gè)路由組件所需的數(shù)據(jù),等到所有的路由組件的數(shù)據(jù)都加載完畢之后,將當(dāng)前store中的state賦值給context.stateresolve了組件實(shí)例。需要注意的是,這時(shí)store中存有首屏渲染組件所需的所有數(shù)據(jù),我們將其值賦值給context.state,renderer如果使用的是template的話,會(huì)將狀態(tài)序列化并通過(guò)注入HTML的方式存儲(chǔ)到window.__INITIAL_STATE__上。

  接下來(lái)我們看瀏覽器渲染入口entry-client.js:

//entry-client.js
import { createApp } from "./app"

const {app, store, router} = createApp();


if (window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
    app.$mount("#app")
})

  瀏覽器激活的邏輯也和上篇文章相類似,唯一不同的是,我們?cè)谝婚_(kāi)始就調(diào)用replaceStatestore中的狀態(tài)state替換成window.__INITIAL_STATE__,這樣客戶端直接可以用此數(shù)據(jù)激活避免二次請(qǐng)求。

  與上一篇文章中的代碼相比,服務(wù)器的server.js代碼保持一致,沒(méi)有其他的修改。現(xiàn)在我們打包看一下我們程序的效果:

  我們發(fā)現(xiàn)服務(wù)端獲取了數(shù)據(jù)渲染了文章列表并且點(diǎn)擊右側(cè)的按鈕可以打開(kāi)文章的鏈接,說(shuō)明客戶端已經(jīng)被正確的激活。但是當(dāng)我們?cè)诓煌酚芍g進(jìn)行切換的時(shí)候,發(fā)現(xiàn)其他的主題并沒(méi)有加載,這是因?yàn)槲覀冎粚?xiě)了服務(wù)端渲染中的數(shù)據(jù)獲取,而在客戶端中不同的路由切換對(duì)應(yīng)的數(shù)據(jù)加載應(yīng)該是客戶端獨(dú)立請(qǐng)求的。因此我們需要添加這部分的邏輯。

  之前我們已經(jīng)說(shuō)過(guò),我們把數(shù)據(jù)請(qǐng)求的邏輯預(yù)置在組件的靜態(tài)函數(shù)asyncData中,客戶端的請(qǐng)求的走這個(gè)邏輯,那么客戶端應(yīng)該在什么時(shí)候去調(diào)用這個(gè)函數(shù)呢?

客戶端請(qǐng)求

  官方文檔中給出兩個(gè)思路,一個(gè)是在路由導(dǎo)航之前就解析好數(shù)據(jù)。一個(gè)是在視圖渲染后再請(qǐng)求數(shù)據(jù)

先請(qǐng)求再渲染

  先請(qǐng)求數(shù)據(jù),等到數(shù)據(jù)請(qǐng)求完畢之后,再渲染組件,要實(shí)現(xiàn)這個(gè)邏輯我們要借助Vue Router中的beforeResolve解析守衛(wèi),在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后,beforeResolve解析守衛(wèi)就被調(diào)用。讓我們改造一下客戶端渲染入口邏輯:

import { createApp } from "./app"

const {app, store, router} = createApp();

if (window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
    router.beforeResolve((to, from, next) => {
        const matched = router.getMatchedComponents(to)
        const prevMatched = router.getMatchedComponents(from)
        // 我們只關(guān)心非預(yù)渲染的組件
        // 所以我們對(duì)比它們,找出兩個(gè)匹配列表的差異組件
        let diffed = false
        const activated = matched.filter((c, i) => {
            return diffed || (diffed = (prevMatched[i] !== c))
        })
        if (!activated.length) {
            return next()
        }
        // 這里如果有加載指示器(loading indicator),就觸發(fā)
        Promise.all(activated.map(c => {
            if (c.asyncData) {
                return c.asyncData({ store, route: to })
            }
        })).then(() => {
            // 停止加載指示器(loading indicator)
            next()
        }).catch(next)
    })
    app.$mount("#app")
})

  上面的beforeResolve中的代碼邏輯,首先比較tofrom路由的匹配路由組件,然后找出兩個(gè)匹配列表的差異組件,再調(diào)用所有差異組件中的asyncData去獲取數(shù)據(jù),待所有數(shù)據(jù)獲取到后,調(diào)用next繼續(xù)執(zhí)行。

  這時(shí)候我們打包并運(yùn)行程序,我們發(fā)現(xiàn)good切換到ask或者share是可以加載數(shù)據(jù)的,但是askshare切換是沒(méi)法加載數(shù)據(jù)的,如下圖:

  這是為什么呢?還記得我們之前專門(mén)為good路由設(shè)置了TopicListCpoy路由組件,為shareask路由設(shè)置了TopicList路由組件,因此shareask切換過(guò)程中而且并不存在差異組件,只是路由參數(shù)發(fā)生了變化。為了解決這個(gè)問(wèn)題,我們?cè)黾咏M件內(nèi)守衛(wèi)解決這個(gè)問(wèn)題:

beforeRouteUpdate: function (to, from, next) {
    this.$options.asyncData({
        store: this.$store,
        route: to
    });
    next()
}

  組件守衛(wèi)beforeRouteUpdate會(huì)在當(dāng)前路由改變,但是仍然屬于該組件被復(fù)用時(shí)調(diào)用,比如動(dòng)態(tài)參數(shù)發(fā)生改變的時(shí)候,beforeRouteUpdate就會(huì)被調(diào)用。這時(shí)我們執(zhí)行加載數(shù)據(jù)的邏輯,問(wèn)題就會(huì)得到解決。在使用先預(yù)取數(shù)據(jù),再加載組件的方式存在一個(gè)易見(jiàn)的問(wèn)題就是會(huì)感受到明顯的卡頓感,因?yàn)槟悴荒鼙WC數(shù)據(jù)什么時(shí)候能請(qǐng)求結(jié)束,如果請(qǐng)求數(shù)據(jù)時(shí)間過(guò)長(zhǎng)而導(dǎo)致組件遲遲不能渲染,用戶體驗(yàn)就會(huì)大打折扣,因此建議在加載的過(guò)程中提供一個(gè)統(tǒng)一的加載指示器,來(lái)盡量降低帶來(lái)的交互體驗(yàn)下降。

先渲染再請(qǐng)求

  先渲染組件再請(qǐng)求數(shù)據(jù)的邏輯比較接近與純客戶端渲染的邏輯,我們將數(shù)據(jù)預(yù)取的邏輯放置在組件的beforeMount或者mounted生命周期函數(shù)中,路由切換之后,組件會(huì)被立即渲染,但是會(huì)存在渲染組件時(shí)不存在完整數(shù)據(jù),因此這個(gè)組件內(nèi)部自身需要提供相應(yīng)加載狀態(tài)。數(shù)據(jù)預(yù)取的邏輯可以在每個(gè)路由組件多帶帶調(diào)用,當(dāng)然也可以通過(guò)Vue.mixin的方式全局實(shí)現(xiàn):

Vue.mixin({
    beforeMount () {
        const { asyncData } = this.$options
        if (asyncData) {
            asyncData({
                store: this.$store,
                route: this.$route
            })
        }
    }
})

  當(dāng)然這種也會(huì)存在我們前面說(shuō)過(guò)的,路由切換但是組件復(fù)用的情況,因此僅僅只在beforeMount做操作做數(shù)據(jù)獲取是不夠的,我們?cè)诼酚蓞?shù)發(fā)生改變但是組件復(fù)用的情況下,也應(yīng)該去請(qǐng)求數(shù)據(jù),這個(gè)問(wèn)題仍然可以通過(guò)組件守衛(wèi)beforeRouteUpdate來(lái)處理。

  到此為止我們已經(jīng)介紹了如何在服務(wù)器渲染中處理數(shù)據(jù)和預(yù)覽的問(wèn)題,需要看源碼的同學(xué)請(qǐng)移步到這里。如果有表達(dá)不正確的地方,歡迎指出,希望大家關(guān)注我的Github博客以及接下來(lái)的系列文章。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/108486.html

相關(guān)文章

  • 無(wú)痛學(xué)會(huì)各種 2 的 Vue2+Vuex2+Webpack2 前后端同構(gòu)渲染

    摘要:它會(huì)檢測(cè)出最大靜態(tài)子樹(shù)就是不需要?jiǎng)討B(tài)性的子樹(shù)并且從渲染函數(shù)中萃取出來(lái)。這樣在每次重渲染的時(shí)候,它就會(huì)直接重用完全相同的同時(shí)跳過(guò)比對(duì)。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個(gè)鏈接~手把手教你從零寫(xiě)一個(gè)簡(jiǎn)單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非常火的vue2前端框架的特點(diǎn)和vue2+vuex2+we...

    fish 評(píng)論0 收藏0
  • 無(wú)痛學(xué)會(huì)各種 2 的 Vue2+Vuex2+Webpack2 前后端同構(gòu)渲染

    摘要:它會(huì)檢測(cè)出最大靜態(tài)子樹(shù)就是不需要?jiǎng)討B(tài)性的子樹(shù)并且從渲染函數(shù)中萃取出來(lái)。這樣在每次重渲染的時(shí)候,它就會(huì)直接重用完全相同的同時(shí)跳過(guò)比對(duì)。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個(gè)鏈接~手把手教你從零寫(xiě)一個(gè)簡(jiǎn)單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非常火的vue2前端框架的特點(diǎn)和vue2+vuex2+we...

    30e8336b8229 評(píng)論0 收藏0
  • 無(wú)痛學(xué)會(huì)各種 2 的 Vue2+Vuex2+Webpack2 前后端同構(gòu)渲染

    摘要:它會(huì)檢測(cè)出最大靜態(tài)子樹(shù)就是不需要?jiǎng)討B(tài)性的子樹(shù)并且從渲染函數(shù)中萃取出來(lái)。這樣在每次重渲染的時(shí)候,它就會(huì)直接重用完全相同的同時(shí)跳過(guò)比對(duì)。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個(gè)鏈接~手把手教你從零寫(xiě)一個(gè)簡(jiǎn)單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非常火的vue2前端框架的特點(diǎn)和vue2+vuex2+we...

    Pluser 評(píng)論0 收藏0
  • Vue同構(gòu)(一): 快速上手

    摘要:當(dāng)然你可以采用頁(yè)面模板的形式,將兩者相分離這里將是應(yīng)用程序標(biāo)記注入的地方中包含了模板當(dāng)然這只是最簡(jiǎn)單的一個(gè)例子,瀏覽器收到的僅僅是對(duì)應(yīng)實(shí)例的代碼,并沒(méi)有將其激活,因此是不可交互的。 前言   首先歡迎大家關(guān)注我的Github博客,也算是對(duì)我的一點(diǎn)鼓勵(lì),畢竟寫(xiě)東西沒(méi)法獲得變現(xiàn),能堅(jiān)持下去也是靠的是自己的熱情和大家的鼓勵(lì)。   同構(gòu)(服務(wù)器渲染)   Vue同構(gòu)也就是我們常說(shuō)的服務(wù)器渲染(...

    archieyang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<