当在 ReactJs React-Redux 中仅创建或更新列表中的一项时,如何停止重新呈现整个项目列表?
How to stop re-rendering a whole list of items when only one item of the list is created or updated in ReactJs React-Redux?
我正在制作这个具有 posts 的网络应用程序,用户可以在其中回答那些 posts。我使用 React-Redux 来管理应用程序的状态。每次我创建或更新特定 post 的答案时,属于该 post 的整个答案列表都会重新呈现,我想停止它并只呈现新创建或更新的答案。我对 post 评论使用了完全相同的方式,并且效果很好。评论不会重新呈现,但答案会。我只是不知道这里有什么问题。请参考下面的代码。
我也试过使用 React.memo()
,但它也不起作用!
答案渲染组件,
export function Answer() {
const classes = useStyles();
const dispatch = useDispatch();
const { postId } = useParams();
const postAnswers = useSelector(state => state.Answers);
const [answers, setAnswers] = React.useState(postAnswers.answers);
React.useEffect(() => {
if(postAnswers.status === 'idle') dispatch(fetchAnswers(postId));
}, [dispatch]);
React.useEffect(() => {
if(postAnswers.answers) handleAnswers(postAnswers.answers);
}, [postAnswers]);
const handleAnswers = (answers) => {
setAnswers(answers);
};
const AnswersList = answers ? answers.map(item => {
const displayContent = item.answerContent;
return(
<Grid item key={item.id}>
<Grid container direction="column">
<Grid item>
<Paper component="form" className={classes.root} elevation={0} variant="outlined" >
<div className={classes.input}>
<Typography>{displayContent}</Typography>
</div>
</Paper>
</Grid>
</Grid>
</Grid>
);
}): undefined;
return(
<Grid container direction="column" spacing={2}>
<Grid item>
<Divider/>
</Grid>
<Grid item>
<Grid container direction="column" alignItems="flex-start" justify="center" spacing={2}>
{AnswersList}
</Grid>
</Grid>
<Grid item>
<Divider/>
</Grid>
</Grid>
);
}
获取 redux 应用的答案,
export const fetchAnswers = (postId) => (dispatch) => {
dispatch(answersLoading());
axios.get(baseUrl + `/answer_api/?postBelong=${postId}`)
.then(answers =>
dispatch(addAnswers(answers.data))
)
.catch(error => {
console.log(error);
dispatch(answersFailed(error));
});
}
Post 个回答,
export const postAnswer = (data) => (dispatch) => {
axios.post(baseUrl + `/answer_api/answer/create/`,
data
)
.then(response => {
console.log(response);
dispatch(fetchAnswers(postBelong)); //This is the way that I update answers state every time a new answer is created or updated
})
.catch(error => {
console.log(error);
});
}
任何帮助都会很棒。谢谢!
添加项目后,您可以从 api 中获取所有项目,以便在状态中重新创建所有项目。如果您为容器组件提供项目的 id 并让选择器将项目作为 JSON 然后解析回对象,您可以记住它并防止重新渲染,但我认为重新渲染可能更好。
以下是项目的记忆 JSON 示例:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const fakeApi = (() => {
const id = ((num) => () => ++num)(1);
const items = [{ id: 1 }];
const addItem = () =>
Promise.resolve().then(() =>
items.push({
id: id(),
})
);
const updateFirst = () =>
Promise.resolve().then(() => {
items[0] = { ...items[0], updated: id() };
});
const getItems = () =>
//this is what getting all the items from api
// would do, it re creates all the items
Promise.resolve(JSON.parse(JSON.stringify(items)));
return {
addItem,
getItems,
updateFirst,
};
})();
const initialState = {
items: [],
};
//action types
const GET_ITEMS_SUCCESS = 'GET_ITEMS_SUCCESS';
//action creators
const getItemsSuccess = (items) => ({
type: GET_ITEMS_SUCCESS,
payload: items,
});
const getItems = () => (dispatch) =>
fakeApi
.getItems()
.then((items) => dispatch(getItemsSuccess(items)));
const update = () => (dispatch) =>
fakeApi.updateFirst().then(() => getItems()(dispatch));
const addItem = () => (dispatch) =>
fakeApi.addItem().then(() => getItems()(dispatch));
const reducer = (state, { type, payload }) => {
if (type === GET_ITEMS_SUCCESS) {
return { ...state, items: payload };
}
return state;
};
//selectors
const selectItems = (state) => state.items;
const selectItemById = createSelector(
[selectItems, (_, id) => id],
(items, id) => items.find((item) => item.id === id)
);
const createSelectItemAsJSON = (id) =>
createSelector(
[(state) => selectItemById(state, id)],
//return the item as primitive (string)
(item) => JSON.stringify(item)
);
const createSelectItemById = (id) =>
createSelector(
[createSelectItemAsJSON(id)],
//return the json item as object
(item) => JSON.parse(item)
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
({ dispatch, getState }) => (next) => (action) =>
//simple thunk implementation
typeof action === 'function'
? action(dispatch, getState)
: next(action)
)
)
);
const Item = React.memo(function Item({ item }) {
const rendered = React.useRef(0);
rendered.current++;
return (
<li>
rendered:{rendered.current} times, item:{' '}
{JSON.stringify(item)}
</li>
);
});
const ItemContainer = ({ id }) => {
const selectItem = React.useMemo(
() => createSelectItemById(id),
[id]
);
const item = useSelector(selectItem);
return <Item item={item} />;
};
const ItemList = () => {
const items = useSelector(selectItems);
return (
<ul>
{items.map(({ id }) => (
<ItemContainer key={id} id={id} />
))}
</ul>
);
};
const App = () => {
const dispatch = useDispatch();
React.useEffect(() => dispatch(getItems()), [dispatch]);
return (
<div>
<button onClick={() => dispatch(addItem())}>
add item
</button>
<button onClick={() => dispatch(update())}>
update first item
</button>
<ItemList />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
刚刚找到问题出在哪里,导致了上面的问题。
在我的状态管理系统中,有一个名为 answers
的操作来处理 post 答案的状态,如下所示。
import * as ActionTypes from '../ActionTypes';
export const Answers = (state = {
status: 'idle',
errMess: null,
answers: []
}, action) => {
switch(action.type) {
case ActionTypes.ADD_ANSWER_LIST:
return {...state, status: 'succeeded', errMess: null, answers: action.payload}
case ActionTypes.ANSWER_LIST_LOADING:
return {...state, status: 'loading', errMess: null, answers: []}
case ActionTypes.ANSWER_LIST_FAILED:
return {...state, status: 'failed', errMess: action.payload, answers: []}
default:
return state;
}
}
这里的问题是我在 ANSWER_LIST_LOADING
和 ANSWER_LIST_FAILED
情况下放入的空数组。每次动作创建者获取新数据时,它都会进入 loading
状态,并在那里得到一个空数组,这会导致整个答案列表不必要地重新呈现和重新创建。所以我按如下方式更改了实现并解决了问题。
export const Answers = (state = {
status: 'idle',
errMess: null,
answers: []
}, action) => {
switch(action.type) {
case ActionTypes.ADD_ANSWER_LIST:
return {...state, status: 'succeeded', errMess: null, answers: action.payload}
case ActionTypes.ANSWER_LIST_LOADING:
return {...state, status: 'loading', errMess: null, answers: [...state.answers]}
case ActionTypes.ANSWER_LIST_FAILED:
return {...state, status: 'failed', errMess: action.payload, answers: [...state.answers]}
default:
return state;
}
}
一直以来,问题都出现在我从未想过的地方。我什至没有在我的问题中提到这个 action
。但是你去吧。
我正在制作这个具有 posts 的网络应用程序,用户可以在其中回答那些 posts。我使用 React-Redux 来管理应用程序的状态。每次我创建或更新特定 post 的答案时,属于该 post 的整个答案列表都会重新呈现,我想停止它并只呈现新创建或更新的答案。我对 post 评论使用了完全相同的方式,并且效果很好。评论不会重新呈现,但答案会。我只是不知道这里有什么问题。请参考下面的代码。
我也试过使用 React.memo()
,但它也不起作用!
答案渲染组件,
export function Answer() {
const classes = useStyles();
const dispatch = useDispatch();
const { postId } = useParams();
const postAnswers = useSelector(state => state.Answers);
const [answers, setAnswers] = React.useState(postAnswers.answers);
React.useEffect(() => {
if(postAnswers.status === 'idle') dispatch(fetchAnswers(postId));
}, [dispatch]);
React.useEffect(() => {
if(postAnswers.answers) handleAnswers(postAnswers.answers);
}, [postAnswers]);
const handleAnswers = (answers) => {
setAnswers(answers);
};
const AnswersList = answers ? answers.map(item => {
const displayContent = item.answerContent;
return(
<Grid item key={item.id}>
<Grid container direction="column">
<Grid item>
<Paper component="form" className={classes.root} elevation={0} variant="outlined" >
<div className={classes.input}>
<Typography>{displayContent}</Typography>
</div>
</Paper>
</Grid>
</Grid>
</Grid>
);
}): undefined;
return(
<Grid container direction="column" spacing={2}>
<Grid item>
<Divider/>
</Grid>
<Grid item>
<Grid container direction="column" alignItems="flex-start" justify="center" spacing={2}>
{AnswersList}
</Grid>
</Grid>
<Grid item>
<Divider/>
</Grid>
</Grid>
);
}
获取 redux 应用的答案,
export const fetchAnswers = (postId) => (dispatch) => {
dispatch(answersLoading());
axios.get(baseUrl + `/answer_api/?postBelong=${postId}`)
.then(answers =>
dispatch(addAnswers(answers.data))
)
.catch(error => {
console.log(error);
dispatch(answersFailed(error));
});
}
Post 个回答,
export const postAnswer = (data) => (dispatch) => {
axios.post(baseUrl + `/answer_api/answer/create/`,
data
)
.then(response => {
console.log(response);
dispatch(fetchAnswers(postBelong)); //This is the way that I update answers state every time a new answer is created or updated
})
.catch(error => {
console.log(error);
});
}
任何帮助都会很棒。谢谢!
添加项目后,您可以从 api 中获取所有项目,以便在状态中重新创建所有项目。如果您为容器组件提供项目的 id 并让选择器将项目作为 JSON 然后解析回对象,您可以记住它并防止重新渲染,但我认为重新渲染可能更好。
以下是项目的记忆 JSON 示例:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
const fakeApi = (() => {
const id = ((num) => () => ++num)(1);
const items = [{ id: 1 }];
const addItem = () =>
Promise.resolve().then(() =>
items.push({
id: id(),
})
);
const updateFirst = () =>
Promise.resolve().then(() => {
items[0] = { ...items[0], updated: id() };
});
const getItems = () =>
//this is what getting all the items from api
// would do, it re creates all the items
Promise.resolve(JSON.parse(JSON.stringify(items)));
return {
addItem,
getItems,
updateFirst,
};
})();
const initialState = {
items: [],
};
//action types
const GET_ITEMS_SUCCESS = 'GET_ITEMS_SUCCESS';
//action creators
const getItemsSuccess = (items) => ({
type: GET_ITEMS_SUCCESS,
payload: items,
});
const getItems = () => (dispatch) =>
fakeApi
.getItems()
.then((items) => dispatch(getItemsSuccess(items)));
const update = () => (dispatch) =>
fakeApi.updateFirst().then(() => getItems()(dispatch));
const addItem = () => (dispatch) =>
fakeApi.addItem().then(() => getItems()(dispatch));
const reducer = (state, { type, payload }) => {
if (type === GET_ITEMS_SUCCESS) {
return { ...state, items: payload };
}
return state;
};
//selectors
const selectItems = (state) => state.items;
const selectItemById = createSelector(
[selectItems, (_, id) => id],
(items, id) => items.find((item) => item.id === id)
);
const createSelectItemAsJSON = (id) =>
createSelector(
[(state) => selectItemById(state, id)],
//return the item as primitive (string)
(item) => JSON.stringify(item)
);
const createSelectItemById = (id) =>
createSelector(
[createSelectItemAsJSON(id)],
//return the json item as object
(item) => JSON.parse(item)
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
({ dispatch, getState }) => (next) => (action) =>
//simple thunk implementation
typeof action === 'function'
? action(dispatch, getState)
: next(action)
)
)
);
const Item = React.memo(function Item({ item }) {
const rendered = React.useRef(0);
rendered.current++;
return (
<li>
rendered:{rendered.current} times, item:{' '}
{JSON.stringify(item)}
</li>
);
});
const ItemContainer = ({ id }) => {
const selectItem = React.useMemo(
() => createSelectItemById(id),
[id]
);
const item = useSelector(selectItem);
return <Item item={item} />;
};
const ItemList = () => {
const items = useSelector(selectItems);
return (
<ul>
{items.map(({ id }) => (
<ItemContainer key={id} id={id} />
))}
</ul>
);
};
const App = () => {
const dispatch = useDispatch();
React.useEffect(() => dispatch(getItems()), [dispatch]);
return (
<div>
<button onClick={() => dispatch(addItem())}>
add item
</button>
<button onClick={() => dispatch(update())}>
update first item
</button>
<ItemList />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
刚刚找到问题出在哪里,导致了上面的问题。
在我的状态管理系统中,有一个名为 answers
的操作来处理 post 答案的状态,如下所示。
import * as ActionTypes from '../ActionTypes';
export const Answers = (state = {
status: 'idle',
errMess: null,
answers: []
}, action) => {
switch(action.type) {
case ActionTypes.ADD_ANSWER_LIST:
return {...state, status: 'succeeded', errMess: null, answers: action.payload}
case ActionTypes.ANSWER_LIST_LOADING:
return {...state, status: 'loading', errMess: null, answers: []}
case ActionTypes.ANSWER_LIST_FAILED:
return {...state, status: 'failed', errMess: action.payload, answers: []}
default:
return state;
}
}
这里的问题是我在 ANSWER_LIST_LOADING
和 ANSWER_LIST_FAILED
情况下放入的空数组。每次动作创建者获取新数据时,它都会进入 loading
状态,并在那里得到一个空数组,这会导致整个答案列表不必要地重新呈现和重新创建。所以我按如下方式更改了实现并解决了问题。
export const Answers = (state = {
status: 'idle',
errMess: null,
answers: []
}, action) => {
switch(action.type) {
case ActionTypes.ADD_ANSWER_LIST:
return {...state, status: 'succeeded', errMess: null, answers: action.payload}
case ActionTypes.ANSWER_LIST_LOADING:
return {...state, status: 'loading', errMess: null, answers: [...state.answers]}
case ActionTypes.ANSWER_LIST_FAILED:
return {...state, status: 'failed', errMess: action.payload, answers: [...state.answers]}
default:
return state;
}
}
一直以来,问题都出现在我从未想过的地方。我什至没有在我的问题中提到这个 action
。但是你去吧。