簡體   English   中英

使用 React 效果時 State 未正確更新

[英]State is not updated properly when using React effects

我正在開發一個小型 todo 應用程序作為使用 React 的練習。 我有這樣的模擬服務:

export default class TodoService {

    constructor(todos) {
        this.todos = new Map();
        todos.forEach(todo => {
            this.todos.set(todo.id, todo);
        });
    }

    findAll() {
        return Array.from(this.todos.values());
    }

    saveTodo(todo) {
        this.todos[todo.id] = todo
    }

    completeTodo(todo) {
        this.todos.delete(todo.id)
    }
}

在我的 React 應用程序中,我有一些 state 包含待辦事項:

const [todos, setTodos] = useState([]);
const [flip, flipper] = useState(true);

const completeTodo = (todo) => {
    todoService.completeTodo(todo);
    flipper(!flip);
}

useEffect(() => {
    setTodos(todoService.findAll());
}, [flip])

completeTodo是一個 function ,我將它傳遞到我的Todo組件中,以便在我想像這樣完成一個 todo 時使用:

import React from "react";
const Todo = ({ todo, completeFn }) => {
    return (
        <form className="todo">
            <div className="form-check">
                <input
                    className="form-check-input"
                    type="checkbox"
                    value=""
                    name={todo.id}
                    id={todo.id}
                    onClick={() => {
                        console.log(`completing todo...`)
                        completeFn(todo)
                    }} />
                <label className="form-check-label" htmlFor={todo.id}>
                    {todo.description}
                </label>
            </div>
        </form>
    )
}
export default Todo

所以發生的情況是,每當用戶單擊復選框completeFn調用todo時,它就會從服務 object 中刪除,並且 state 應該更新,但最奇怪的事情發生了。

TodoService.completeTodo()被調用時,todo 被正確刪除,但是當findAll()被調用時,舊的 todo 仍然存在! 如果我將內容寫入控制台,我可以看到該項目被刪除,然后在我調用findAll時以某種方式傳送回來。 為什么會這樣? 我這是因為一些我不明白的 React 魔法?

編輯:更瘋狂的是,如果我將其修改為僅對初始加載使用效果,如下所示:

const [todos, setTodos] = useState([]);

const completeTodo = (todo) => {
    todoService.completeTodo(todo);
    setTodos(todoService.findAll());
}

useEffect(() => {
    setTodos(todoService.findAll());
}, [])

我得到一個非常奇怪的結果:

在此處輸入圖像描述

誰可以給我解釋一下這個?

Edit2:這是一個完整的可重現示例(沒有index.html<div id="root"></div> )。

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Todo = ({ todo, completeFn }) => {
    return (
        <div>
            <input
                type="checkbox"
                name={todo.id}
                id={todo.id}
                onClick={() => {
                    console.log(`completing todo...`)
                    completeFn(todo)
                }} />
            <label className="form-check-label" htmlFor={todo.id}>
                {todo.description}
            </label>
        </div>
    )
}

class TodoService {

    constructor(todos) {
        this.todos = new Map();
        todos.forEach(todo => {
            this.todos.set(todo.id, todo);
        });
    }

    findAll() {
        return Array.from(this.todos.values());
    }

    saveTodo(todo) {
        this.todos[todo.id] = todo
    }

    completeTodo(todo) {
        this.todos.delete(todo.id)
    }
}

const App = () => {

    let todoService = new TodoService([{
        id: 1,
        description: "Let's go home."
    }, {
        id: 2,
        description: "Take down the trash"
    }, {
        id: 3,
        description: "Play games"
    }]);

    const [todos, setTodos] = useState([]);
    const [flip, flipper] = useState(true);

    const completeTodo = (todo) => {
        todoService.completeTodo(todo);
        flipper(!flip);
    }

    useEffect(() => {
        setTodos(todoService.findAll());
    }, [flip])

    return (
        <div>
            {todos.map(todo => <Todo key={todo.id} todo={todo} completeFn={completeTodo} />)}
        </div>
    )
};
ReactDOM.render(<App />, document.getElementById("root"));

在這種情況下,您不需要調用useEffect 您已經在useEffect中添加了一個依賴項,可以使用它來停止無限循環。 但這里沒有必要。 你並沒有真正做任何fetch

您可以將代碼更新為這樣。

import React, { useState, useCallback, useEffect } from "react";

const Todo = ({ todo, completeFn }) => {
  const handleOnclick = useCallback(() => {
    // useCallback since function is passed down from parent
    console.log(`completing todo...`);
    completeFn(todo);
  }, [completeFn, todo]);

  return (
    <div>
      <input
        type="checkbox"
        name={todo.id}
        id={todo.id}
        onClick={handleOnclick}
      />
      <label className="form-check-label" htmlFor={todo.id}>
        {todo.description}
      </label>
    </div>
  );
};

class TodoService {
  constructor(todos) {
    this.todos = new Map();
    todos.forEach(todo => {
      this.todos.set(todo.id, todo);
    });
  }

  findAll() {
    console.log(Array.from(this.todos.values()));
    return Array.from(this.todos.values());
  }

  saveTodo(todo) {
    this.todos[todo.id] = todo;
  }

  completeTodo(todo) {
    this.todos.delete(todo.id);
  }
}

const todoService = new TodoService([
  {
    id: 1,
    description: "Let's go home."
  },
  {
    id: 2,
    description: "Take down the trash"
  },
  {
    id: 3,
    description: "Play games"
  }
]);

export default function App() {
  const [todos, setTodos] = useState([]); // Set initial state

  const completeTodo = todo => {
    todoService.completeTodo(todo);
    setTodos(todoService.findAll()); // Update state
  };

  useEffect(() => {
    setTodos(todoService.findAll());
  }, []); // Get and update from service on first render only

  return (
    <div>
      {todos.map(todo => (
        <Todo key={todo.id} todo={todo} completeFn={completeTodo} />
      ))}
    </div>
  );
}

工作示例

https://codesandbox.io/s/cranky-hertz-sewc5?file=/src/App.js

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM