欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

react同構(gòu)實踐之實現(xiàn)自己的同構(gòu)模板

 更新時間:2019年03月13日 11:19:43   作者:Winder  
這篇文章主要介紹了react同構(gòu)實踐之實現(xiàn)自己的同構(gòu)模板,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一開始想學(xué)學(xué)服務(wù)端渲染,腦海中第一個浮現(xiàn)出來的就是next.js這種成熟的方案??戳艘粌商?,有趣,優(yōu)雅,但是封裝好了,原理不甚清楚,也感覺無法靈活嵌合到老項目上去。于是看各種資料,想整理出同構(gòu)的線索,一步一步地實現(xiàn)自己的同構(gòu)模板。相關(guān)代碼可查看我的GitHub。感謝閱讀??!

TODO List

  • 數(shù)據(jù):如何保持前后端應(yīng)用狀態(tài)一致
  • 路由:路由在服務(wù)端和客戶端中的匹配方案
  • 代碼:同構(gòu),哪些地方可以共享,哪些地方需要差異化
  • 靜態(tài)資源:服務(wù)端如何引入css/圖片等
  • ssr直出資源:服務(wù)端在渲染路由頁面時如何匹配css/chunks資源
  • 打包方案:服務(wù)端和瀏覽器端如何寫各自的webpack配置文件
  • SEO: head頭處理方案

同構(gòu)的基礎(chǔ)

正常的網(wǎng)頁運行,需要生成dom,在dom樹loaded之后由js綁定相關(guān)的dom事件,監(jiān)聽頁面的交互。服務(wù)端并不具備dom的執(zhí)行環(huán)境,因而所有的服務(wù)端渲染其實都是返回了一個填充了初始數(shù)據(jù)的靜態(tài)文本。在react中,除了常用的render這個用于生成dom的方法,還提供了renderToString,renderToStaticMarkup方法用來生成字符串,由于VitualDOM的存在,結(jié)合這些方法就可以像以前的字符串模板那樣生成普通的字符串,返回給客戶端接管,再接著進行事件相關(guān)的綁定。最新的React v16+使用hydrate和ssr配套,能讓客戶端把服務(wù)端的VitualDOM渲染出來后得以復(fù)用,客戶端加載js后不會重刷一邊,減小了開銷,也避免瀏覽器重刷dom時帶來的閃屏體驗。而react的組件,還是和往常寫spa一樣編寫,前后端共享。不同的只是入口的渲染方法換了名字,且客戶端會掛載dom而已。

// clinet.js
ReactDom.hydrate(<App />, document.getElementById('app'))

// server.js
const html = ReactDom.renderToString(<App />)

同構(gòu)后網(wǎng)站運行流程圖

盜用一張圖,來自阿里前端。乍一看,ssr與csr的區(qū)別就在于2 3 4 5,spa模式簡單粗暴地返回一個空白的html頁面,然后在11里才去加載數(shù)據(jù)進行頁面填充,在此之前,頁面都處于空白狀態(tài)。而ssr則會根據(jù)路由信息,提前獲取該路由頁面的初始數(shù)據(jù),返回頁面時已經(jīng)有了初步的內(nèi)容,不至于空白,也便于搜索引擎收錄。

路由匹配

瀏覽器端的路由匹配還是照著spa來做應(yīng)該無需費心。略過了...

服務(wù)端的路由需要關(guān)注的,一個是后端服務(wù)的路由(如koa-router)匹配的問題,一個是匹配到react應(yīng)用后react-router路由表的匹配問題。

服務(wù)端路由,可通過/react前綴來和api接口等其他區(qū)別開來,這種路由匹配方式甚至能讓服務(wù)端渲染能同時支持老項目諸如ejs等的模板渲染方式,在系統(tǒng)升級改造方面可實現(xiàn)漸進式地升級。

// app.js文件(后端入口)
import reactController from './controllers/react-controller'
// API路由
app.use(apiController.routes())

// ejs頁面路由
app.use(ejsController.routes())

// react頁面路由
app.use(reactController.routes())

// react-controller.js文件
import Router from 'koa-router'

const router = new Router({
 prefix: '/react'
})

router.all('/', async (ctx, next) => {

 const html = await render(ctx)

 ctx.body = html

})

export default router

react-router專供了給ssr使用的StaticRouter接口,稱之為靜態(tài)的路由。誠然,服務(wù)端不像客戶端,對應(yīng)于一次網(wǎng)絡(luò)請求,路由就是當前的請求url,是唯一的,不變的。在返回ssr直出的頁面后,頁面交互造成地址欄的變化,只要用的是react-router提供的方法,無論是hash方式,還是history方式,都屬于瀏覽器端react-router的工作了,于是完美繼承了spa的優(yōu)勢。只有在輸入欄敲擊Enter,才會發(fā)起新一輪的后臺請求。

import { StaticRouter } from 'react-router-dom'
 const App = () => {

  return (
   <Provider store={store}>

    <StaticRouter
     location={ctx.url}
     context={context}>
     
     <Layout />

    </StaticRouter>

   </Provider>
  )
 }

應(yīng)用狀態(tài)數(shù)據(jù)管理

以往的服務(wù)端渲染,需要在客戶端網(wǎng)頁下載后馬上能看到的數(shù)據(jù)就放在服務(wù)器提前準備好,可延遲展示,通過ajax請求的數(shù)據(jù)的交互邏輯放在頁面加載的js文件中去。

換成了react,其實套路也是一樣一樣的。但是區(qū)別在于:

傳統(tǒng)的字符串模板,組件模板是彼此分離的,可各自單獨引入數(shù)據(jù),再拼裝起來形成一份html。而在react的ssr里,頁面只能通過defaultValue和defaultProps一次性render,無法rerender。

不能寫死defaultValude,所以只能使用props的數(shù)據(jù)方案。在執(zhí)行renderToString之前,提前準備好整個應(yīng)用狀態(tài)的所有數(shù)據(jù)。全局的數(shù)據(jù)管理方案可考慮redux和mobx等。

需要準備初始渲染數(shù)據(jù),所以要精準獲取當前地址將要渲染哪些組件。react-router-config和react-router同源配套,是個支持靜態(tài)路由表配置的工具,提供了matchRoutes方法,可獲得匹配的路由數(shù)組。

import { matchRoutes } from 'react-router-config'

import loadable from '@loadable/component'

const Root = loadable((props) => import('./pages/Root'))
const Index = loadable(() => import("./pages/Index"))
const Home = loadable(() => import("./pages/Home"))

const routes = [
 {
  path: '/',
  component: Root,
  routes: [
   {
    path: '/index',
    component: Index,
   },
   {
    path: '/home',
    component: Home,
    syncData () => {}
    routes: []
   }
  ]
 }
]

router.all('/', async (url, next) => {
 const branch = matchRoutes(routes, url)
})

組件的初始數(shù)據(jù)接口請求,最美的辦法當然是定義在各自的class組件的靜態(tài)方法中去,但是前提是組件不能被懶加載,不然獲取不到組件class,當然也無法獲取class static method了,很多使用@loadable/component(一個code split方案)庫的開發(fā)者多次提issue,作者也明示無法支持。不支持懶加載是絕對不可能的了。所以委屈一下代碼了,在需要的route對象中定義一個asyncData方法。

服務(wù)端

// routes.js
{
 path: '/home',
 component: Home,
 asyncData (store, query) {
  const city = (query || '').split('=')[1]
 
  let promise = store.dispatch(fetchCityListAndTemperature(city || undefined))
  
  let promise2 = store.dispatch(setRefetchFlag(false))
 
  return Promise.all([promise, promise2])
  return promise
 }
}

// render.js
import { matchRoutes } from 'react-router-config'
import createStore from '../store/redux/index'

const store = createStore()
const branch = matchRoutes(routes, url)

const promises = branch.map(({ route }) => {
 // 遍歷所有匹配路由,預(yù)加載數(shù)據(jù)
 return route.asyncData
  ? route.asyncData(store, query)
  : Promise.resolve(null)

})
// 完成store的預(yù)加載數(shù)據(jù)初始化工作
await Promise.all(promises)
// 獲取最新的store
const preloadedState = store.getState()

const App = (props) => {

 return (
  <Provider store={store}>

   <StaticRouter
    location={ctx.url}
    context={context}>
    
    <Layout />

   </StaticRouter>

  </Provider>
 )
}
// 數(shù)據(jù)準備好后,render整個應(yīng)用
const html = renderToString(<App />)

// 把預(yù)加載的數(shù)據(jù)掛載在`window`下返回,客戶端自己去取
return 
  <html>
   <head></head>
   <body>
    <div id="app">${html}</div>
    <script>
     window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
    </script>
   </body>
  </html>

客戶端

為保證兩端的應(yīng)用數(shù)據(jù)一致,客戶端也要使用同一份數(shù)據(jù)初始化一次redux的store,再生成應(yīng)用。如果兩者的dom/數(shù)據(jù)不一致,導(dǎo)致瀏覽器接管的時候dom重新生成了一次,在開發(fā)模式下的時候,控制臺會輸出錯誤信息,開發(fā)體驗完美。后續(xù)ajax的數(shù)據(jù),在componentDidMount和事件中去執(zhí)行,和服務(wù)端的邏輯天然剝離。

// 獲取服務(wù)端提供的初始化數(shù)據(jù)
const preloadedState = window.__PRELOADED_STATE__ || undefined

delete window.__PRELOADED_STATE__

// 客戶端store初始化
const store = createStore(preloadedState)

const App = () => {

 return (
  <Provider store={store}>

   <BrowserRouter>

    <Layout />
    
   </BrowserRouter>

  </Provider>
 )
}

// loadableReady由@loadabel/component提供,在code split模式下使用
loadableReady().then(() => {
 
 ReactDom.hydrate(<App />, document.getElementById('app'))

})

服務(wù)端調(diào)用的接口客戶端也必須有。這就帶來了如何避免重復(fù)請求的問題。我們知道componentDidMount方法只執(zhí)行一次,如果服務(wù)器已經(jīng)請求的數(shù)據(jù)帶有一個標識,就可以根據(jù)這個標識決定是否在客戶端需要發(fā)起一個新的請求了,需要注意的是判斷完成后重置該標識。

import { connect } from 'react-redux'

@connect(
 state => ({
  refetchFlag: state.weather.refetchFlag,
  quality: state.weather.quality
 }),
 dispatch => ({
  fetchCityListAndQuality: () => dispatch(fetchCityListAndQuality()),
  setRefetchFlag : () => dispatch(setRefetchFlag(true))
 })
)
export default class Quality extends Component {
 componentDidMount () {

  const {
   location: { search },
   refetchFlag,
   fetchCityListAndQuality,
   setRefetchFlag
  } = this.props

  const { location: city } = queryString.parse(search)

  refetchFlag 
   ? fetchCityListAndQuality(city || undefined)
   : setRefetchFlag()
 }
}

打包方案

客戶端打包

我想說的是“照舊”。因為在瀏覽器端運行的還是spa。入門級的具體見github,至于如何配置得賞心悅目,用起來得心應(yīng)手,根據(jù)項目要求各顯神通吧。

服務(wù)端打包

和客戶端的異同:

同:

需要bable兼容不同版本的js語法

webpack v4+/babel v7+ ... 真香

... 留白

異:

入口文件不一樣,出口文件不一樣

這里既可以把整個服務(wù)端入口app.js作為打包入口,也可以把react路由的起點文件作為打包入口,配置輸出為umd模塊,再由app.js去require。以后者為例(好處在于升級改造項目時盡可能地降低對原系統(tǒng)的影響,排查問題也方便,斷點調(diào)試什么的也方便):

// webpack.server.js
const webpackConfig = {
 entry: {
  server: './src/server/index.js'
 },
 output: {
  path: path.resolve(__dirname, 'build'),
  filename: '[name].js',
  libraryTarget: 'umd'
 }
}

// app.js
const reactKoaRouter = require('./build/server').default
app.use(reactKoaRouter.routes())

css、image資源正常來說服務(wù)端無需處理,如何繞開

偷懶,還沒開始研究,占個坑

require的是node自帶的模塊時避免被webpack打包

const serverConfig = { ... target: 'node' }

require第三方模塊時如何避免被打包

const serverConfig = { ... externals: [ require('webpack-node-externals')() ]

生產(chǎn)環(huán)境代碼無需做混淆壓縮

... 留白

服務(wù)端直出時資源的搜集

服務(wù)端輸出html時,需要定義好css資源、js資源,讓客戶端接管后下載使用。如果沒啥追求,可以直接把客戶端的輸出文件全加上去,暴力穩(wěn)妥,簡單方便。但是上面提到的@loadable/component庫,實現(xiàn)了路由組件懶加載/code split功能后,也提供了全套服務(wù),配套套裝的webpack工具,ssr工具,幫助我們做搜集資源的工作。

// webpack.base.js
const webpackConfig = {
 plugins: [ ..., new LoadablePlugin() ]
}

// render.js
import { ChunkExtractor } from '@loadable/server'

const App = () => {

 return (
  <Provider store={store}>

   <StaticRouter
    location={ctx.url}
    context={context}>
    
    <Layout />

   </StaticRouter>

  </Provider>
 )
}

const webStats = path.resolve(
 __dirname,
 '../public/loadable-stats.json', // 該文件由webpack插件自動生成
)

const webExtractor = new ChunkExtractor({ 
 entrypoints: ['client'],  // 為入口文件名
 statsFile: webStats
})


const jsx = webExtractor.collectChunks(<App />)

const html = renderToString(jsx)

const scriptTags = webExtractor.getScriptTags()
const linkTags = webExtractor.getLinkTags() 
const styleTags = webExtractor.getStyleTags()

const preloadedState = store.getState()

const helmet = Helmet.renderStatic()

return `
 <html>
  <head>
   ${helmet.title.toString()}
   ${helmet.meta.toString()}
   ${linkTags}
   ${styleTags}
  </head>
  <body>
   <div id="app">${html}</div>
   <script>
    window.STORE = 'love';
    window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
   </script>
   ${scriptTags}
  </body>
 </html>
`

SEO信息

上面已經(jīng)透露了。使用了一個react-helmet庫。具體用法可查看官方倉庫,信息可直接寫在組件上,最后根據(jù)優(yōu)先級提升到head頭部。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • react版模擬亞馬遜人機交互菜單的實現(xiàn)

    react版模擬亞馬遜人機交互菜單的實現(xiàn)

    本文主要介紹了react版模擬亞馬遜人機交互菜單的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • React?Hooks項目實戰(zhàn)

    React?Hooks項目實戰(zhàn)

    React?Hooks是React?16.8版本引入的新特性,它使得在函數(shù)組件中也能夠使用狀態(tài)(state)和其他React特性,本文就來詳細介紹一下React?Hooks項目實戰(zhàn),感興趣的可以了解一下
    2023-11-11
  • React?函數(shù)式組件和類式組件詳情

    React?函數(shù)式組件和類式組件詳情

    這篇文章主要介紹了React函數(shù)式組件和類式組件詳情,React是組件化的的JS庫,組件化也是React的核心思想,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • React Native可定制底板組件Magic Sheet使用示例

    React Native可定制底板組件Magic Sheet使用示例

    這篇文章主要為大家介紹了React Native可定制的底板組件Magic Sheet使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • nodejs和react實現(xiàn)即時通訊簡易聊天室功能

    nodejs和react實現(xiàn)即時通訊簡易聊天室功能

    這篇文章主要介紹了nodejs和react實現(xiàn)即時通訊簡易聊天室功能,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • React Hooks與setInterval的踩坑問題小結(jié)

    React Hooks與setInterval的踩坑問題小結(jié)

    本文主要介紹了React Hooks與setInterval的踩坑,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • React render核心階段深入探究穿插scheduler與reconciler

    React render核心階段深入探究穿插scheduler與reconciler

    這篇文章主要介紹了React render核心階段穿插scheduler與reconciler,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-11-11
  • react mobx 基本用法示例小結(jié)

    react mobx 基本用法示例小結(jié)

    mobx是一個輕量級的狀態(tài)管理器,所以很簡單(單一全局數(shù)據(jù)使用class)類有g(shù)et 數(shù)據(jù)方法,本文通過示例代碼介紹react mobx 基本用法,感興趣的朋友有一起看看
    2023-11-11
  • React-router v4 路由配置方法小結(jié)

    React-router v4 路由配置方法小結(jié)

    本篇文章主要介紹了React-router v4 路由配置方法小結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 淺談React多個setState會調(diào)用幾次

    淺談React多個setState會調(diào)用幾次

    在React的生命周期鉤子和合成事件中,多次執(zhí)行setState,會被調(diào)用幾次,本文就詳細的介紹一下,感興趣的可以了解一下
    2021-11-11

最新評論