助力ssr,使用concent為nextjs應用加點料

鍾正楷發表於2020-12-19

開源不易,感謝你的支援,❤ star concent^_^

序言

nextjs是一個非常流行的 React 服務端渲染應用框架,它很輕量,簡單易上手,社群活躍,所以當我們使用react寫一個需要ssr(server side render)的應用的話,基本都會首選nextjsconcent是一個新生代的react狀態管理方案,它內建依賴收集系統,同時兼具有0入侵、可預測、漸進式、高效能的特點,並提供了lifecylecomposition api等靈活的api且寫法超級簡單,讓你輕鬆駕馭超大規模的react應用。

Hello next

這裡我們將使用create-next-app命令來安裝一個基礎的next示例應用

npx create-next-app hello-next

執行完畢後,可以看到一個如下的目錄結構

|____public
|____pages
| |____ _app.js   // next應用預設的根元件
| |____index.js   // 預設首頁
| |____api        // api路由檔案
| | |____hello.js 

之後我們在專案根目錄執行npm run dev將看到一個由next驅動的ssr預設首頁

Hello concent

這裡我們將使用create-react-app命令來安裝一個基礎的concent示例應用

npx create-react-app hello-concent --template concent-ts

執行完畢後,可以看到一個如下的目錄結構

|____index.tsx
|____App.tsx
|____types            // store的型別定義處
|____features           // 功能元件列表
| |____counter          // counter功能
| | |____Counter.tsx        // counter元件
| | |____model            // counter模型(包含state,reducer,computed)
|____models            // 其它全域性通用的模型定義
|____configs

進入專案目錄執行npm i,然後執行npm start即可看到一個預設的計數器頁面

你也可以點選這裡線上瞭解和編輯它。

當然了在已有的專案裡整合concent裡也超級簡單,因為它無需頂層提供Provider,只需要提前配置好模型即可。

import { run } from 'concent';

run({ // 定義一個counter模型
  counter: {
    state: { num: 1, bigNum: 10 },
    reducer: {
      add(payload, moduleState) {
        return { num: moduleState + 1 };
      },
      async asyncAddBig() {
        await new Promise(resolve => setTimeout(resolve, 1000));
        return { bigNum: moduleState + 10 };
      }
    },
    computed: {
      doubleNum: ({ num }) => num * 2, // 僅當num發生變化才觸發此函式
    }
  }
})

之後就可以全域性即插即用啦,類元件和函式元件都可以用同樣的方式去讀取資料或呼叫方法,敲重點啦,如果ui處是有條件語句控制是否要消費狀態或衍生資料的話,推薦延遲解構的寫法,這樣可以讓concent在每一輪渲染完畢後收集到檢視對資料的最小粒度依賴

// ###### 函式元件
function Demo(){
  // 如 state 和 moduleComputed 是按需讀取的,推薦延遲解構的寫法
  const { state: { num, numBig }, moduleComputed: { doubleNum }, mr } = useConcent('counter'); 
  // ... ui 邏輯,綁資料、綁方法
}

// ###### 類元件
const DemoCls = register('counter')(
  class DemoCls extends React.Component{
   render(){
      const { state: { num, numBig }, moduleComputed: { doubleNum }, mr } = this.ctx; 
      // ... ui 邏輯,綁資料、綁方法
    }
  }
)

在next裡引入concent

next的基礎示例目錄裡有個_app.js檔案,它是next應用的根元件

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

因使用concent之前必需提前配置好模型,所以我們只需提前建立一個runConcent.js檔案

import { run } from 'concent'
import * as models from './models';

run(models);

然後在_app.js檔案引入即可,這樣根元件下的所有子元件都能夠正確獲取到store的資料和調動store的方法了。

import '../styles/globals.css'
+ import './runConcent'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

接著我們在next的pages目錄下建立一個counter.js檔案,代表這是一個頁面元件,這樣瀏覽器端可以用/counter路由來訪問到這個元件的渲染檢視了。

import React from 'react'
import { useConcent } from 'concent'
import router from 'next/router'

// use next/router to do browser side router jump
function toHomePage(){
  router.push('/');
}

export default function Counter() {
  const { state, mr, moduleComputed } = useConcent('home')

  return (
    <div>
      this is counter page
      <h1>num: {state.num}</h1>
      <h1>doubleNum: {moduleComputed.doubleNum}</h1>
      <button onClick={mr.add}>add</button>
      <button onClick={toHomePage}>to home page</button>
    </div>
  );
}

大功告成,一個接入了concentnext應用就這樣產生了,是不是特別簡單呢?^_^

支援預渲染

next提供兩種級別的預渲染介面,即getServerSidePropsgetStaticProps,兩種的區別是執行時機不同,getServerSideProps是每次請求頁面都會執行,而getStaticProps是構建時執行,我們先處理getServerSideProps這種情況吧,看看如何集合concent做預渲染支援。

首先我們不考慮concent的存在,在next裡做預渲染支援,只需要在你的頁面元件裡暴露一個getServerSideProps介面即可。

// 此函式在每次請求改頁面時被呼叫
export async function getServerSideProps() {
  // 呼叫外部 API 獲取博文列表
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // 通過返回 { props: posts } 物件,PostPage 元件在渲染時將接收到 `posts` 引數
  return {
    props: { posts },
  }
}

function PostPage({ posts }) { // 這裡接收到了 posts 引數
  // Render posts...
}

export default PostPage

之所以Blog能夠接到posts,除了暴露這個getServerSideProps這個介面之外,我們再觀察一下_app.js這個根元件檔案內容,可以發現關鍵點所在!

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}
export default MyApp

引數列表裡的pageProps即是getServerSideProps返回結果裡props指向的物件,然後next將其透傳到目標頁面元件上,所以我們才能夠在PostPage引數列表裡解構出posts

所以我們的切入點就可以從這裡入手了,我們把getStaticProps的返回結果做一下格式約束,形如{module:string, state: object}這樣的結構,然後在_app.js檔案裡記錄到store即可

// 此函式在每次請求時被呼叫
export async function getServerSideProps() {
  // 呼叫外部 API 獲取博文列表
  await delay();
  const posts = [
    { id: 1, name: 'post1 -----' },
    { id: 2, name: 'post2 --- welcome to use concent' },
  ];
  // 這個返回物件會透傳給根元件的pageProps,在此返回狀態所屬的模組和狀態實體物件
  // 在那裡將狀態記錄到store
  return {
    props: {
      module: 'test',
      state: { posts },
    }
  };
}

此時的根元件檔案改變如下

import '../styles/globals.css';
+ import './runConcent';
+ import { setState } from 'concent';

function MyApp({ Component, pageProps }) {
  // 這裡記錄 getServerSideProps 的返回狀態到store的對應模組
+  if (pageProps.module) {
+    setState(pageProps.module, pageProps.state);
+  }
  return <Component {...pageProps} />
}
export default MyApp;

然後我們實現的頁面元件post-page程式碼如下

const PostList = React.memo(function () {
  const { state } = useConcent('test');
  return (
    <div>
      {state.posts.map(item => <h3 key={item.id}>{item.name}</h3>)}
    </div>
  );
});

const PostLength = React.memo(function () {
  const { state } = useConcent('test');
  return <h1>{state.posts.length}</h1>;
});

export default function PostPage() {
  return (
    <div>
      <h1>this is post page</h1>
      <PostList />
      <PostLength />
      <button onClick={toHomePage}>to home page</button>
    </div>
  );
}

接著我們開啟瀏覽器訪問/post-page頁面吧,點選檢視原始碼將會看到這是一個伺服器端預渲染的頁面

同理,我們也可將getServerSideProps替換為getStaticProps,上面的整個流程將依然正常工作,歡迎各位看官clone示例程式碼來親自體驗一下。

git clone https://github.com/concentjs/ssr-demo-1

附錄

doc

CloudBase CMS

歡迎小哥哥們來撩CloudBase CMS ,打造一站式雲端內容管理系統,它是雲開發推出的,基於 Node.js 的 Headless 內容管理平臺,提供了豐富的內容管理功能,安裝簡單,易於二次開發,並與雲開發的生態體系緊密結合,助力開發者提升開發效率。

concent已為其管理後臺提供強力支援,新版的管理介面更加美觀和體貼了。

FFCreator

也歡迎小哥哥們來撩FFCreator,它是一個基於node.js的輕量、靈活的短視訊加工庫。您只需要新增幾張圖片或視訊片段再加一段背景音樂,就可以快速生成一個很酷的視訊短片。

FFCreator是一種輕量又簡單的解決方案,只需要很少的依賴和較低的機器配置就可以快速開始工作。並且它模擬實現了animate.css90%的動畫效果,您可以輕鬆地把 web 頁面端的動畫效果轉為視訊,真的很給力。

相關文章