React s-s-r路由器-导航到子路由时的redux状态丢失

Posted

技术标签:

【中文标题】React s-s-r路由器-导航到子路由时的redux状态丢失【英文标题】:React s-s-r router - redux state loss when navigating to sub route 【发布时间】:2021-10-16 10:14:45 【问题描述】:

我在服务器上渲染反应一切正常,除了导航到子路由时,redux 丢失状态和来自后端的数据不出现。数据显示在 redux 开发工具中。但是,状态不会在页面刷新时丢失。

这是一个说明我的意思的例子:

website.com/category/books to website.com/category/toys

App.js:

import React from 'react'
import  Switch, Route  from 'react-router-dom'
import HomeScreen from './Screens/HomeScreen'
import CategoryScreen from './Screens/CategoryScreen'

const App = () => 
  return (
    <Switch>
      <Route path='/' component=HomeScreen exact />
      <Route path='/category/:name' component=CategoryScreen />
    </Switch>

  )


export default App;

index.js:

import React from 'react'
import  Provider  from 'react-redux'
import  hydrate  from 'react-dom'
import  BrowserRouter  from 'react-router-dom'
import store from './store'
import './index.css'
import App from './App'

hydrate(
  <Provider store=store>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
)

store.js:

import  createStore, combineReducers, applyMiddleware  from 'redux'
import thunk from 'redux-thunk'
import  composeWithDevTools  from 'redux-devtools-extension'
import  productListReducer  from './reducers/productReducers'
import  categoryListReducer  from './reducers/categoryReducers'

const reducer = combineReducers(
    productList: productListReducer,
    categoryList: categoryListReducer,
)

const initialState = 

const middleware = [thunk]

const loadState = () => 
    try 
        const serializedState = localStorage.getItem('state')
        if (serializedState === null) 
            return undefined
        
        return JSON.parse(serializedState)
     catch (e) 
        return undefined
    


const saveState = (state) => 
    try 
        const serializedState = JSON.stringify(state)
        localStorage.setItem('state', serializedState)
     catch (e) 

    


const persistedState = loadState()

const store = createStore(
    reducer, persistedState, composeWithDevTools(applyMiddleware(...middleware))
)

store.subscribe(() => 
    saveState(store.getState())
)

export default store

用于 s-s-r 的前端 server.js:

import path from 'path'
import fs from 'fs'
import express from 'express'
import React from 'react'
import  StaticRouter  from 'react-router'
import ReactDOMServer from 'react-dom/server'
import  Provider  from 'react-redux'
import store from '../src/store'
import App from '../src/App'
import  createProxyMiddleware  from 'http-proxy-middleware'

const PORT = 3000
const app = express()

app.use('/api/products', createProxyMiddleware( target: 'http://98.51.100.255:5000', changeOrigin: true ))
app.use('/api/categories', createProxyMiddleware( target: 'http://98.51.100.255:5000', changeOrigin: true ))

const router = express.Router()

const serverRenderer = (req, res, next) => 

  app.get('/*', function (req, res) 
    res.sendFile(path.join(__dirname, '../build/index.html'), function (err) 
      if (err) 
        res.status(500).send(err)
      
    )
  )

  const context = 

  fs.readFile(path.resolve('./build/index.html'), 'utf8', (err, data) => 
    if (err) 
      console.error(err)
      return res.status(500).send('An error occurred')
    
    return res.send(
      data.replace(
        '<div id="root"></div>',
        `<div id="root">
        $ReactDOMServer.renderToString(
          <Provider store=store>
            <StaticRouter location=req.url context=context>
              <App />
            </StaticRouter>
          </Provider>
        )
        </div>`
      )
    )
  )


router.use('^/$', serverRenderer)

router.use(
  express.static(path.resolve(__dirname, '..', 'build'))
)

app.use(router)

app.listen(PORT, () => 
  console.log(`s-s-r running on port $PORT`)
)

categoryscreen.js:

import React,  useEffect, useState, useRef  from 'react'
import  useDispatch, useSelector  from 'react-redux'
import CategoryHeader from '../components/CategoryHeader'
import Product from '../components/Product'
import  listProducts  from '../actions/productActions'

function CategoryScreen( match ) 
    let ProductMatch = match.params.name

    const dispatch = useDispatch()

    const productList = useSelector(state => state.productList)
    const  products  = productList

    useEffect(() => 
        dispatch(listProducts())
    , [dispatch])

    return (
        <>
            <h3>ProductMatch</h3>
            <div>
                products.filter(product => product.SubCategory == ProductMatch)
                    .map((product) => (
                        <Product product=product />
                    ))
                
            </div>

        </>
    )


export default CategoryScreen

产品列表操作:

import axios from 'axios'
import 
    PRODUCT_LIST_REQUEST,
    PRODUCT_LIST_SUCCESS,
    PRODUCT_LIST_FAIL,
 from '../constants/productConstants'


export const listProducts = () => async (dispatch) => 
    try 
        dispatch( type: PRODUCT_LIST_REQUEST )

        const  data  = await axios.get('/api/products')

        dispatch(
            type: PRODUCT_LIST_SUCCESS,
            payload: data
        )
     catch (error) 
        dispatch(
            type: PRODUCT_LIST_FAIL,
            payload: error.response && error.response.data.message
                ? error.response.data.message : error.message
        )
    

编辑:添加 categoryscreen.js 和 redux 操作

【问题讨论】:

您能否分享CategoryScreen,因为该组件似乎没有处理新的 URL? @DrewReese 添加了分类屏幕 谢谢。假设您正确地看到 name (ProductMatch) 匹配参数随着路线更改从“书籍”更新为“玩具”是否安全?您能否澄清您所指的丢失状态? productList? @DrewReese 是的 productmatch 确实会随着路线的变化而更新。并且正在从 productlist 操作中发送数据。我将 productList 操作添加到帖子中 【参考方案1】:

所以目前还不清楚问题是什么。 URL 发生变化,组件看到新的ProductMatch 值,它被重新渲染,它应该通过ProductMatch 类别过滤productList 状态并重新映射产品。

我看到这里唯一缺少的部分是为映射提供 React 键。在没有键的情况下,React 基本上将使用孩子的索引作为 React 键,并且由于“书”子类别的索引 n 将与“玩具”子类别的索引 n 相同,我怀疑 React 只是放弃重新渲染你的 UI。

根据每个被映射的 product 的唯一属性提供一个 React 键,例如 id。如果产品没有此保证,那么我建议在处理您的 redux 状态时扩充您的产品数据,以便为此添加 GUID。

products.filter(product => product.SubCategory == ProductMatch)
  .map((product) => (
    <Product
      key=product.id // <-- add React key
      product=product
    />
  ))

【讨论】:

以上是关于React s-s-r路由器-导航到子路由时的redux状态丢失的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 React-Router 和 React-Redux 将道具发送到子路由组件?

后退按钮后反应路由器“历史”和“位置”不匹配

使用 React 路由器以编程方式导航

使用 React 路由器以编程方式导航

选择反应路由器时的setState(React)

React 导航栏在屏幕上,即使它不在路由器上