配合使用redux-thunk

本文介绍如何使用 Redux-thunk 实现客户端和服务端的异步数据加载,包括设置模拟服务器、创建 Reducer 和 Actions、在组件中展示数据、服务端数据加载优化等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

假设需求,我们值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/bootstrap@3.3.7/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的使用,可以看出来和客户端不一样的地方有,但是不多,就几个关键的点吗,学会的就好了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值