【问题标题】:How would I use local storage for a to do list?我如何将本地存储用于待办事项列表?
【发布时间】:2020-06-22 13:18:18
【问题描述】:

我被要求有一个待办事项列表并通过本地存储保存每个任务(用户提供的以及原始的)。我的老师对完全不同的东西做了一个非常简单的演示,我花了几个小时试图弄清楚。当我查看解决方案时,老实说,我无法弄清楚。它看起来很复杂,我什至不知道从哪里开始。如果有人能给我任何提示,那就太棒了!

我的代码:

let ul = document.querySelector('ul');

let newItem = document.querySelector('input[type=text]');
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');

function output() {
    let newTodo = document.createElement('li');
    newTodo.innerText = newItem.value;
    newTodo.classList.add('todo');
    let ulAppend = ul.append(newTodo);
    ul.append(newTodo);
    let checkboxAppend = newTodo.append(checkbox);
    newTodo.append(checkbox);
    newItem.value = '';
}

let button = document.querySelector('.btn');
button.addEventListener('click', output);

ul.addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
        e.target.remove();
    } else if (e.target.tagName === 'INPUT') {
        e.target.parentElement.classList.toggle('finished');
    }
});

我老师的代码/本地存储方案:

const todoForm = document.getElementById("newTodoForm");
const todoList = document.getElementById("todoList");

// retrieve from localStorage
const savedTodos = JSON.parse(localStorage.getItem("todos")) || [];
for (let i = 0; i < savedTodos.length; i++) {
  let newTodo = document.createElement("li");
  newTodo.innerText = savedTodos[i].task;
  newTodo.isCompleted = savedTodos[i].isCompleted ? true : false;
  if (newTodo.isCompleted) {
    newTodo.style.textDecoration = "line-through";
  }
  todoList.appendChild(newTodo);
}

todoForm.addEventListener("submit", function(event) {
  event.preventDefault();
  let newTodo = document.createElement("li");
  let taskValue = document.getElementById("task").value;
  newTodo.innerText = taskValue;
  newTodo.isCompleted = false;
  todoForm.reset();
  todoList.appendChild(newTodo);

  // save to localStorage
  savedTodos.push({ task: newTodo.innerText, isCompleted: false });
  localStorage.setItem("todos", JSON.stringify(savedTodos));
});

todoList.addEventListener("click", function(event) {
  let clickedListItem = event.target;

  if (!clickedListItem.isCompleted) {
    clickedListItem.style.textDecoration = "line-through";
    clickedListItem.isCompleted = true;
  } else {
    clickedListItem.style.textDecoration = "none";
    clickedListItem.isCompleted = false;
  }

  // breaks for duplicates - another option is to have dynamic IDs
  for (let i = 0; i < savedTodos.length; i++) {
    if (savedTodos[i].task === clickedListItem.innerText) {
      savedTodos[i].isCompleted = clickedListItem.isCompleted;
      localStorage.setItem("todos", JSON.stringify(savedTodos));
    }
  }
});

尽管我的代码更简单(至少据我所知),但它的工作原理与他的代码完全一样。

【问题讨论】:

  • 您可以将键值对存储在 localstorage 中,值应该是字符串。想法可能类似于,在页面加载时检查数据是否存在于本地存储中,并显示它。当您提交时,您应该将数据推送到本地存储。有方法:本地存储的getItem和setItem方法。请检查一下。

标签: javascript json local-storage


【解决方案1】:

本地存储将 JSON 对象保存到用户的计算机。您应该创建一个待办事项数组,将该数组附加到每个新待办事项中,然后将该项目设置为本地存储。

let ul = document.querySelector('ul');

const savedTodos = JSON.parse(localStorage.getItem("todos")) || []; // Retrieves local storage todo OR creates empty array if none exist
let newItem = document.querySelector('input[type=text]');
let checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');

function output() {
    let newTodo = document.createElement('li');
    newTodo.innerText = newItem.value;
    newTodo.classList.add('todo');
    ul.append(newTodo);
    newTodo.append(checkbox);

    savedTodos.push({task: newItem.value, isCompleted: false}); // Appends the new todo to array
    localStorage.setItem("todos", JSON.stringify(savedTodos)); //Converts object to string and stores in local storage

    newItem.value = '';
}

【讨论】:

  • 它停止工作了?还是只是 localStorage 部分?我使用了您提供的代码,只添加了 3 行。
  • 本地存储不起作用。我刷新它并没有留在页面上,尽管它保存在 localStorage
【解决方案2】:

我已经用一些 cmets 对您发布的解决方案进行了注释,以帮助您逐步完成它。

// Retrieve elements and store them in variables
const todoForm = document.getElementById("newTodoForm");
const todoList = document.getElementById("todoList");

// Get data stored in localStorage under the key "todos".
// The data type will be a string (local storage can only store strings).
// JSON is a global object that contains methods for working with data represented as strings.
// The `||` syntax is an OR operator and is used here to set an empty array as a fallback in case `localStorage` is empty
const savedTodos = JSON.parse(localStorage.getItem("todos")) || [];

// Create a loop the same length as the list of todos
for (let i = 0; i < savedTodos.length; i++) {
  // Create an <li> element in memory (does not appear in the document yet)
  let newTodo = document.createElement("li");
  // Set the inner text of that new li with the contents from local storage.
  // The savedTodos[i] is accessing data in the localStorage array.
  // The [i] is a different number each loop.
  // The `.task` is accessing 'task' property on the object in the array.
  newTodo.innerText = savedTodos[i].task;
  // Create a new property on the element called `isCompleted` and assign a boolean value.
  // This is only accessible in code and will not show up when appending to the DOM.
  newTodo.isCompleted = savedTodos[i].isCompleted ? true : false;
  // Check the value we just set.
  if (newTodo.isCompleted) {
    // Create a style for the element if it is done (strike it out)
    newTodo.style.textDecoration = "line-through";
  }
  // Actually append the new element to the document (this will make it visible)
  todoList.appendChild(newTodo);
}

// `addEventListener` is a function that registers some actions to take when an event occurs.
// The following tells the browser - whenever a form is submitted, run this function.
todoForm.addEventListener("submit", function(event) {
  // Don't try to send the form data to a server. Stops page reloading.
  event.preventDefault();
  // Create a <li> element in memory (not yet visible in the document)
  let newTodo = document.createElement("li");
  // Find element in the document (probably a input element?) and access the text value.
  let taskValue = document.getElementById("task").value;
  // Set the text of the <li>
  newTodo.innerText = taskValue;
  // Set a property on the <li> call `isCompleted`
  newTodo.isCompleted = false;
  // Empty out all the input fields in the form
  todoForm.reset();
  // Make the new <li> visible in the document by attaching it to the list
  todoList.appendChild(newTodo);

  // `push` adds a new element to the `savedTodos` array. In this case, an object with 2 properties.
  savedTodos.push({ task: newTodo.innerText, isCompleted: false });
  // Overwrite the `todos` key in local storage with the updated array.
  // Use the JSON global object to turn an array into a string version of the data
  // eg [1,2,3] becomes "[1,2,3]"
  localStorage.setItem("todos", JSON.stringify(savedTodos));
});

// This tells the browser - whenever the todoList is clicked, run this function.
// The browser will call the your function with an object that has data about the event.
todoList.addEventListener("click", function(event) {
  // the `target` of the event is the element that was clicked.
  let clickedListItem = event.target;

  // If that element has a property called `isCompleted` set to true
  if (!clickedListItem.isCompleted) {
    // update the styles and toggle the `isCompleted` property.
    clickedListItem.style.textDecoration = "line-through";
    clickedListItem.isCompleted = true;
  } else {
    clickedListItem.style.textDecoration = "none";
    clickedListItem.isCompleted = false;
  }

  // The code above changes the documents version of the data (the elements themselves)
  // This loop ensures that the array of todos data is kept in sync with the document
  // Loop over the array
  for (let i = 0; i < savedTodos.length; i++) {
    // if the item in the array has the same text as the item just clicked...
    if (savedTodos[i].task === clickedListItem.innerText) {
      // toggle the completed state
      savedTodos[i].isCompleted = clickedListItem.isCompleted;
      // Update the localStorage with the new todos array.
      localStorage.setItem("todos", JSON.stringify(savedTodos));
    }
  }
});

请记住,您的待办事项列表中有 2 个状态来源。一个是文档的外观,另一个是todos 数据的数组。确保这两者保持同步会带来很多挑战。

如果文档以某种方式显示其中一个列表项被划掉,但您的数据数组显示所有todos未完成,那么哪个版本是正确的?这里没有正确的答案,但是状态管理将是您将来设计应用程序时可能会考虑的问题。 Redux 是一个很好的 js 库,具有很好理解的模式,有助于解决这个问题。希望这最后的评论不会混淆太多。祝你好运!

【讨论】:

  • 是否可以在不使用循环的情况下将所有内容保存在本地存储中?此外,在老师的解决方案中,他的删除线与我的做法不同。单击 li 时,我设置了一个类切换。如果有意义的话,我只是想找出一个可以与我的代码一起使用而无需过多编辑的解决方案。
  • 状态源有两种。 DOM 元素本身和数据数组。当一个 todo 被点击时,你需要改变 DOM 元素的视觉风格,但你也需要更新数组中的数据。循环用于更新数组中的项目。如果您不循环并更新数组中的一项,则您的数据和视图不同步。在不循环和更新数组的情况下设置localStorage 只会再次重新保存旧数组数据。我认为在这种情况下你需要循环。
【解决方案3】:

重要的部分是(反)序列化数据。这意味着:

  • 读自localStorage (JSON.parse(localStorage.getItem("todos")) || [])

    我们添加默认的[],因为如果todos 键不存在,我们将得到null,并且我们需要一个列表

  • 保存到localStorage (localStorage.setItem("todos", JSON.stringify(savedTodos)))

我们需要JSON.parse 及其互补操作JSON.stringify 来解析和保存字符串,因为localStorage 只能存储字符串。

在您的情况下,您需要从 localStorage 读取数据并渲染初始列表。要将其保存到 localStorage,您必须再次序列化数据。请参阅下面的 sn-ps(链接到工作 JSFIDDLE,因为下面的示例在 StackOverflow 沙箱环境中不起作用):

let ul = document.querySelector('ul');

let newItem = document.querySelector('input[type=text]');

const Store = {
  serialize () {
    return [].slice.call(document.querySelectorAll("li")).map(c => {
      return {
        text: c.textContent,
        finished: c.querySelector("input").checked
      }
    })
  },
  get () {
    return JSON.parse(localStorage.getItem("todos")) || []
  },
  save () {
    return localStorage.setItem("todos", JSON.stringify(Store.serialize()))
  }
}

const firstItems = Store.get()
firstItems.forEach(it => {
  output(it.text, it.finished)
})

function output(v, finished) {
  let newTodo = document.createElement('li');
  newTodo.innerText = v || newItem.value;
  newTodo.classList.add('todo');
  let ulAppend = ul.append(newTodo);
  ul.append(newTodo);

  // Create a checkbox for each item
  let checkbox = document.createElement('input');
  if (finished) {
    checkbox.checked = true
  }
  checkbox.setAttribute('type', 'checkbox');

  let checkboxAppend = newTodo.append(checkbox);
  newTodo.append(checkbox);
  newItem.value = '';
}

let button = document.querySelector('.btn');
button.addEventListener('click', () => {
    output()
  Store.save()
});

ul.addEventListener('click', function(e) {
  if (e.target.tagName === 'LI') {
    e.target.remove();
  } else if (e.target.tagName === 'INPUT') {
    e.target.parentElement.classList.toggle('finished');
  }
  // Update the value in localStorage when you delete or add a new item
  Store.save()
});
<ul></ul>
<input type="text" /> <button class="btn">Submit</button>

我添加了Store 变量以简化您在localStorage 中获取和设置数据的方式。

serialize 方法将从列表中读取 TODO。 document.querySelectorAll("li") 返回 NodeList,但通过 [].slice.call(...) 我们将其转换为 Array

【讨论】:

  • 这看起来和我老师的回答一样复杂。我只是在学习和看这个我不知道几件事。例如,以下是做什么/意味着什么? : serialize () { return [].slice.call(document.querySelectorAll("li")).map(c => { return { text: c.textContent // 另外,您是否将变量存储设置为多个函数,是那是什么?
  • 看起来变量存储是函数的对象。但是我看不到它们是在哪里定义的?你可以为 variable.function() 定义一个变量吗? (const firstItems = Store.get()) 编辑:哦,等等。 js里面已经定义了这些函数吧?
  • @Cutecat42 当我们创建这样一个对象时,它就是在那里定义的函数集合:myObj = { myMethod () {...} }
  • 我刚刚尝试了该代码,但它没有在本地存储中保存任何内容:(
  • @Cutecat42 我更新了我的代码。我忘了保存添加新的待办事项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-06
  • 2020-09-24
  • 1970-01-01
  • 2019-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多