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状态丢失的主要内容,如果未能解决你的问题,请参考以下文章