【问题标题】: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?当在 ReactJs React-Redux 中仅创建或更新列表中的一个项目时,如何停止重新渲染整个项目列表?
【发布时间】:2021-03-20 19:36:47
【问题描述】:

我正在制作这个 Web 应用程序,它有帖子,用户可以在这些帖子中给出答案。我使用 React-Redux 来管理应用程序的状态。每次我创建或更新特定帖子的答案时,属于该帖子的整个答案列表都会重新呈现,我想停止它并仅呈现新创建或更新的答案。我对 post cmets 使用了完全相同的方式,并且效果很好。评论不会重新呈现,但答案会。我只是无法弄清楚这里有什么问题。请参考下面的代码。

我也尝试使用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));
    });
}

发布答案,

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);
    });
}

任何帮助都会很棒。谢谢!

【问题讨论】:

    标签: javascript reactjs redux react-redux rerender


    【解决方案1】:

    添加项目后,您会从 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>

    【讨论】:

    • 非常感谢您的回答,为什么您认为每次创建或更新一个答案时重新呈现答案列表会更好?会不会影响申请质量?还是有其他方法可以做到这一点?
    • @janithahn 如果您不进行大量渲染,那么它会使代码更简单。但是,如果您发现性能问题,您可以尝试我的答案中使用的记忆选择器。
    • 啊,好吧。知道了!非常感谢!
    【解决方案2】:

    我刚刚发现导致上述问题的问题出在哪里。 在我的状态管理系统中,有一个名为 answers 的操作来处理帖子答案的状态,如下所示。

    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_LOADINGANSWER_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。但是你去吧。

    【讨论】:

    • 天啊...你帮我省了很多痛苦... :D
    • 很高兴为您提供帮助!
    猜你喜欢
    • 2018-04-10
    • 2020-10-30
    • 2020-11-15
    • 1970-01-01
    • 2015-06-25
    • 2018-03-27
    • 2018-10-30
    • 2018-08-18
    • 2021-09-28
    相关资源
    最近更新 更多