未注册该状态的 Redux 存储已更改



当我的化简器运行时,存储确实更改为新值,但 redux devtools 显示存储始终处于新值,而不是旧值。我是editSnippetReducerFunction减速器中的变异状态还是什么?

const addSnippetReducerFunction = (state: any, action): any => {
return Object.assign({}, state, {
snippets: [
...state.snippets,
{
text: action.payload.text,
id: action.payload.id
}
]
})
}
const editSnippetReducerFunction = (state: any, action): any => {
const newSnippets = state.snippets.map(snippet => {
if (snippet.id === action.payload.id) {
snippet.text = action.payload.text
return snippet
} else {
return snippet
}
})
return { snippets: newSnippets, ...state}
}
const removeSnippetReducerFunction = (state: any, action): any => {
const newSnippets = state.snippets.filter(snippet => snippet.id !== action.payload.id)
return { snippets: newSnippets, history: [] }
}
export const rootReducer: any = createReducer(initialState, {
ADD_SNIPPET: addSnippetReducerFunction,
EDIT_SNIPPET: editSnippetReducerFunction,
REMOVE_SNIPPET: removeSnippetReducerFunction
})

将调度具有正确详细信息的操作。只有editSnippetReducerFunction减速器功能存在此问题,上面显示的其他减速器确实可以正常工作。

编辑:如果我停止在组件上使用 react-reduxconnect并将action移动到connect编辑并工作的父组件,它实际上有效。

connected 时不起作用的组件:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import Button from '@material-ui/core/Button'
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
import CodeIcon from '@material-ui/icons/Code'
import { styles } from './snippet.style.js'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import { removeSnippet } from '../app/action'
import { bindActionCreators } from 'redux'
import type { Dispatch } from 'redux'
import { connect } from 'react-redux'
const mapDispatchToProps = (dispatch: Dispatch): any => {
return bindActionCreators(
{
removeSnippet: removeSnippet
},
dispatch
)
}
class SnippetComponent extends Component<any, any> {
constructor(props) {
super(props)
this.state = {
anchorEl: undefined
}
}
handleClick = event => {
this.setState({ anchorEl: event.currentTarget })
}
handleClose = () => {
this.setState({ anchorEl: null })
}
handleRemove = () => {
this.props.removeSnippet({snippetId: this.props.snippet.id})
}
render = () => {
return (
<ListItem
button
onClick={() => this.props.onSnippetClick(this.props.snippet)}
className={this.props.classes.listItem}>
<ListItemIcon>
<CodeIcon />
</ListItemIcon>
<ListItemText
style={{
marginRight: '100px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
primary={this.props.snippet.text}
/>
<ListItemSecondaryAction>
<Button
variant="fab"
color="primary"
aria-owns={this.state.anchorEl ? 'simple-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}
className={this.props.classes.iconHover}
style={{
marginRight: '50px',
boxShadow: 'none',
color: 'white'
}}
aria-label="Add">
<MoreHorizIcon />
</Button>
<Menu
id="simple-menu"
anchorEl={this.state.anchorEl}
open={Boolean(this.state.anchorEl)}
onClose={this.handleClose}>
<MenuItem onClick={this.handleRemove}>Remove Snippet</MenuItem>
<MenuItem onClick={this.handleClose}>Share</MenuItem>
</Menu>
</ListItemSecondaryAction>
</ListItem>
)
}
}
SnippetComponent.propTypes = {
classes: PropTypes.object.isRequired,
snippet: PropTypes.object.isRequired,
addToCart: PropTypes.func.isRequired
}
const Snippet = withStyles(styles)(
connect(
undefined,
mapDispatchToProps
)(SnippetComponent)
)
export default withStyles(styles)(Snippet)

父组件:

import React, { Component } from 'react'
import './App.css'
import brace from 'brace'
import AceEditor from 'react-ace'
import 'brace/mode/javascript'
import 'brace/theme/gruvbox'
import Button from '@material-ui/core/Button'
import AddIcon from '@material-ui/icons/Add'
import { bindActionCreators, createStore } from 'redux'
import type { Dispatch } from 'redux'
import { addSnippet, editSnippet, removeSnippet } from './action'
import { connect, Provider } from 'react-redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import PropTypes from 'prop-types'
import Grid from '@material-ui/core/Grid'
import Snippet from '../snippet/Snippet'
import List from '@material-ui/core/List'
import { rootReducer } from './app.reducer.js'
import type { Props, AppState } from './app.model.js'
import { appHeaderHeight } from './app.style.js'
import { withStyles } from '@material-ui/core/styles'
import { styles } from './app.style.js'
import Header from '../header/Header'
import ConfirmDialog from '../confirm/ConfirmDialog'
// and so is this. proptypes needs it in initial state and also mapstatetoprops
const mapStateToProps = (
state: any,
ownProps: { buttonColour: string }
): any => ({
snippets: state.snippets,
history: state.history,
buttonColour: ownProps.buttonColour
})
const mapDispatchToProps = (dispatch: Dispatch): any => {
return bindActionCreators(
{
addSnippet: addSnippet,
editSnippet: editSnippet
},
dispatch
)
}
class AppComponent extends Component<Props, AppState> {
constructor(props) {
super(props)
this.state = {
width: 0,
height: 0,
editor: React.createRef(),
saveButtonDisabled: true,
editorValue: '',
open: false,
lastClickedSnippet: ''
}
}
componentDidMount = () => {
this.updateWindowDimensions()
window.addEventListener('resize', this.updateWindowDimensions)
}
componentWillUnmount = () => {
window.removeEventListener('resize', this.updateWindowDimensions)
}
updateWindowDimensions = () => {
this.setState({
width: window.innerWidth,
height: window.innerHeight
})
}
onEditorChange = editorValue => {
if (editorValue.length > 5) {
this.setState({ saveButtonDisabled: false })
} else {
this.setState({ saveButtonDisabled: true })
}
this.setState({ editorValue: editorValue })
}
onSaveButtonClick = () => {
this.setState({ saveButtonDisabled: true })
if (this.state.lastClickedSnippet) {
this.props.editSnippet({
snippetId: this.state.lastClickedSnippet.id,
snippetText: this.state.editorValue
})
this.setState({ lastClickedSnippet: undefined })
} else {
this.props.addSnippet({
text: this.state.editor.current.editor.getValue()
})
}
this.setState({ editorValue: '' })
}
onSnippetClick = (snippet: Snippet) => {
this.setState({ lastClickedSnippet: snippet })
this.setState({ open: true })
}
onDialogClose = value => {
this.setState({ value, open: false })
}  
handleOk = () => {
this.setState({ editorValue: this.state.lastClickedSnippet.text })
this.onDialogClose(this.state.value)
};
handleCancel = () => {
this.setState({ lastClickedSnippet: undefined })
this.onDialogClose(this.state.value)
};
render = () => {
let allSnippets = []
if (this.props.snippets) {
allSnippets = this.props.snippets.map(snippet => (
<Snippet
snippet={snippet}
key={snippet.id}
onSnippetClick={this.onSnippetClick}
editSnippet={this.props.editSnippet}
/>
))
}
return (
<div className="App">
<ConfirmDialog
handleOk={this.handleOk}
handleCancel={this.handleCancel}
open={this.state.open}
onDialogClose={this.onDialogClose}
value={this.state.value}
/>
<Header />
<div
className={this.props.classes.bodyContainer}
style={{ height: this.state.height - appHeaderHeight - 70 }}>
<Grid
container
spacing={0}
alignItems={'flex-start'}
direction={'row'}
justify={'flex-start'}>
<Grid
item
sm={12}
md={6}
className={this.props.classes.leftGrid}
style={{ height: this.state.height - appHeaderHeight - 70 }}>
<Button
className={this.props.classes.saveButton}
variant="fab"
color="secondary"
aria-label="Add"
disabled={this.state.saveButtonDisabled}
onClick={this.onSaveButtonClick}>
<AddIcon />
</Button>
<AceEditor
mode="javascript"
theme="gruvbox"
width="100%"
value={this.state.editorValue}
onChange={this.onEditorChange}
height={this.state.height - appHeaderHeight - 70}
name="editor"
editorProps={{ $blockScrolling: true }}
ref={this.state.editor}
/>
</Grid>
<Grid
item
sm={12}
md={6}
className={this.props.classes.rightGrid}
style={{ height: this.state.height - appHeaderHeight - 70 }}>
<List component="nav" className={this.props.classes.navList} />
{allSnippets}
</Grid>
</Grid>
</div>
</div>
)
}
}
const App = withStyles(styles)(
connect(
mapStateToProps,
mapDispatchToProps
)(AppComponent)
)
AppComponent.propTypes = {
snippets: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
text: PropTypes.string.isRequired
}).isRequired
).isRequired,
history: PropTypes.array,
addSnippet: PropTypes.func.isRequired
}
const store = createStore(rootReducer, composeWithDevTools())
const SnippetApp = () => (
<Provider store={store}>
<App />
</Provider>
)
export default SnippetApp

我是editSnippetReducerFunction减速器中的变异状态还是什么?

是的,你是。您正在更改要更新的截图。

您可能希望改为执行以下操作:

const editSnippetReducerFunction = (state: any, action): any => ({
...state,
snippets: state.snippets.map(snippet =>
snippet.id === action.payload.id
? {...snipped, text: action.payload.text}
: snipped
)),
});

另外,请注意:

return {snippets: newSnippets, ...state}

与此不同:

return {...state, snippets: newSnippets}

对于第一个,如果state有一个名为snippets的属性,则将使用该属性,而不是您的newSnippets。另一方面,对于第二个,snippets属性将使用newSnippets更新

。基本上,第一个是糖:

return Object.assign({}, {snippets: newSnippets}, state);

而第二个相当于:

return Object.assign({}, state, {snippets: newSnippets});

无论如何,我建议的editSnippetReducerFunction实现解决了您在原始实现中遇到的 2 个不同问题。