假設需求,我們值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的使用,可以看出來和客戶端不一樣的地方有,但是不多,就幾個關鍵的點嗎,學會的就好了