配合使用redux-thunk

假設需求,我們值home頁面需要加載用戶列表

  • 首先修改服務器, 這裏主要是給幾個假數據,在請求接口的時候使用
// 1.在src下新建api目錄
// 2. 在src/api目錄下 新建server.js, 內容如下
const express = require('express')
const cors = require('cors')
const app = express()

// 處理跨域
app.use(cors({
  orgin: 'localhost:3000'
}))
// 定義用戶列表
let users = [
  {id: 1, name: 'leo'},
  {id: 2, name: 'Jhon'}
]
// 獲取用戶接口
app.get('/api/users', function (req, res) {
  res.json(users)
})
app.listen(4000)

// 然後使用 nodemon 運行腳本, 瀏覽器使用localhost訪問服務是否正常

創建 需要的reducer

既然使用redux-thunk, 肯定是需要新建reducer和actions的,這裏一步步的來創建

  • reducer
// 1. src/store/reducers中新建home.js
// 2. 內容如下
import * as types from '../action-types'
// 定義初始化狀態
let initState = {
    list: [], // 保存用戶列表
}

export default function (state = initState, action) {
    switch (action.type) {
        case types.SET_HOME_LIST:
        
        return {list: action.payload}
    default:
        return state;
    }
}

// 然後在src/store/reducers/index.js 引入home,並何必該reducer

  • actions
// 1. 在src/store/actions中創建home.js
// 2. 在home.js 輸入以下內容,異步獲取數據
import * as types from '../action-types'
import axios from 'axios'
export default {
    getHomeList () {
        return function (dispatch, getState) {
          // 服務器地址, 自己看自己的
            return axios.get('http://localhost:4000/api/users').then(res=>{
            let {data} = res
            dispatch({
                type: types.SET_HOME_LIST,
                payload: data
            }) 
        })
        }
    }
}

在home組件中使用該數據

上邊完成了定義數據,模擬接口,獲取數據,ruuder和對應的actions,接下來我們將數據在home頁面展示出來
和客戶端使用一樣,用connect連接倉庫

// src/container/Home 組件修改如下
import React, {Component} from 'react';
import { connect } from 'react-redux'
import actions from '../../store/actions/home'

class Home extends Component {
    componentDidMount () {
      // 獲取數據 在本地切換的時候需要異步加載數據
      this.props.getHomeList()
    }
    render () {
        return (<div className="row">
            <h1>Home</h1>
            <ul className="list-group">
                {this.props.list.map(item => <li key={item.id} className="list-group-item">{item.name}</li>)
                }
            </ul>
        </div>)
    }
}
// 連接倉庫
Home = connect(
    state => state.home,
    actions
)(Home)

export default Home;

// 至此, 客戶端異步加載數據完成,瀏覽器刷新頁面即可

服務端異步加載數據

隨然可以加載數據了,但是我們發現是我們本地以後發起了一個ajax請求,然後獲取的數據,這個肯定不是我們想要的,
並服務器裏直接返回來的更直接一些,而且還能減少請求, 代碼看下便:

  • home 組件新增屬性

思路: 給home 組建新增一個屬性(loadData),賦值爲一個function,我們在路由渲染的時候檢測是否擁有此屬性,如果有該屬性,直接調用即可
loadData =》 函數接受一個參數,就說store,等我們獲取數據的時候方便存儲到store中去

import React, {Component} from 'react';
import { connect } from 'react-redux'
import actions from '../../store/actions/home'

class Home extends Component {
    componentDidMount () {
        // 獲取數據 在本地切換的時候需要異步加載數據
        if (this.props.list.length === 0) {
            this.props.getHomeList()
        }
    }
    render () {
        return (<div className="row">
            <h1>Home</h1>
            <ul className="list-group">
                {this.props.list.map(item => <li key={item.id} className="list-group-item">{item.name}</li>)
                }
            </ul>
        </div>)
    }
}
Home = connect(
    state => state.home,
    actions
)(Home)

// 此方法是用來異步加載數據, 並且放到倉庫中去
Home.loadData = function (store) {
    // dispatch的返回值就是action
    return store.dispatch(actions.getHomeList())
}
export default Home;

修改路由

修改路由,新增一個loadData屬性,值爲組建的loadData
執行路由渲染的時候會用到

// src/routers.js 導出路由修改如下
export default [
    {
        path: '/',
        component: Home,
        key: 'home',
        exact: true,
        loadData: Home.loadData // 加載數據,如果有此配置項,那麼則意味着需要加載異步數據
    },
    {
        path: '/counter',
        component: Counter,
        key: 'counter'
    }
]

修改渲染模板

// src/client/index.js 修改後如下
import React, {Fragment} from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, Route } from 'react-router-dom'
import routers from '../routes'
import Header from '../components/Header'
import { Provider } from 'react-redux' // 配合使用redux
import { getClientStore } from '../store'

// hydrate 表示把服務端渲染未完成的工作完成,比如綁定事件完成
ReactDOM.hydrate(
<Provider store={getClientStore()}>
<BrowserRouter>
    <Fragment>
        <Header />
        <div className="container" style={{marginTop: 70}}>
            {routers.map( route => {
                return <Route {...route}/>
            })}
        </div>
    </Fragment>
</BrowserRouter>
</Provider>, document.getElementById('root'))

修改服務端腳本

這裏主要處理在渲染頁面的時候檢查是否存在loadData屬性,存在的話進行加載數據

// src/server/render.js
import { StaticRouter } from 'react-router-dom'
import Header from '../components/Header'
import routes from '../routes'
import React, {Fragment} from 'react'
import {renderToString} from 'react-dom/server'
import { Provider } from 'react-redux'
import { getServerStore } from '../store'
import { Route, matchPath } from 'react-router-dom'

export default  function (ctx, next) {
    let context = {}
    let store = getServerStore()
    // 獲取要渲染的組件
    // matchPath 是路由提供的工具方法, 可以用來判斷路徑和路由是否匹配
    let matchRoutes = routes.filter( route => (matchPath(ctx.req.url, route)))
    let promises = []
    // 遍歷需要渲染的模板列表, 看是否需要異步加載數據
    matchRoutes.map(route => {
        // 判斷是否需要加載異步數據
        if (route.loadData) { // 如果需要加載數據,調用其loadData方法
            promises.push(route.loadData(store))
        }
    })
    // 這裏主要是可能會有多個promise,等全部處理完了在返回
    return Promise.all(promises).then(() => {
        // 創建倉庫的時候, 倉庫裏已經有默認值
        console.log(store.getState())
        let html = renderToString(
            <Provider store={store}>
                <StaticRouter context={{}} location={ctx.req.url}>
                    <Fragment>
                        <Header />
                        <div className="container" style={{marginTop: 70}}>
                            {routes.map( route => ( <Route {...route}/>))}
                        </div>
                    </Fragment>
                </StaticRouter>
            </Provider>
        )
        
        ctx.body = `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta http-equiv="X-UA-Compatible" content="ie=edge">
            <title>Document</title>
            <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
        </head>
        <body>
            <div id="root">${html}</div>
            <script>
                // 這裏主要是保證服務器端的store和本地創建的store一樣
                // 一定要放在client前邊,因爲會在client中調用
                // 同步服務器端的state對象
                window.content = {
                    state: ${JSON.stringify(store.getState())}
                }
            </script>
            <script src="/client.js"></script>
        </body>
        </html>
        `
    })
}

修改sore

這邊主要是因爲服務端沒有dom,所以需要處理,我們首先件渲染哪裏將store得知作爲全局對象放在一個script中,這裏引用

import { createStore, applyMiddleware } from 'redux'
// import saga from 'redux-saga'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import reducers from './reducers'
export function getServerStore () {
    return createStore(
        reducers,
        applyMiddleware(thunk, logger)
    )
}


export function getClientStore () {
   // 在這裏拿到我們之前掛載在window上的state, 然後作爲初始值
    let initState = window.content.state
    return createStore(
        reducers,
        initState,
        applyMiddleware(thunk, logger)
    )
}

總結

以上時簡單的redux-think的使用,可以看出來和客戶端不一樣的地方有,但是不多,就幾個關鍵的點嗎,學會的就好了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章