Rei*_*ick 8 javascript functional-programming
我最近一直在学习一些使用JavaScript的函数式编程,并且想通过编写一个仅包含函数式编程的简单ToDo应用程序来将我的知识用于测试。但是,我不确定如何以一种纯函数的方式存储列表状态,因为不允许函数产生副作用。让我举例说明。
假设我有一个名为“ Item”的构造函数,它只需要完成任务,并带有一个uuid来标识该项目。我还有一个items数组,其中包含所有当前项目,以及一个“ add”和“ delete”函数,如下所示:
function Item(name){
this.name = name;
this.uuid = uuid(); //uuid is a function that returns a new uuid
}
const items = [];
function addItem(name){
const newItem = new Item(name);
items.push(newItem);
}
function deleteItem(uuid){
const filteredItems = items.filter(item => item.uuid !== uuid);
items = filteredItems
}
Run Code Online (Sandbox Code Playgroud)
现在,它可以完美运行,但是如您所见,函数不是纯粹的:它们确实有副作用,不会返回任何东西。考虑到这一点,我尝试使其功能如下:
function Item(name){
this.name = name;
this.uuid = uuid(); //uuid is a function that returns a new uuid
}
const items = [];
function addItem(array, constructor, name){
const newItem = new constructor(name);
return array.concat(newItem);
}
function removeItem(array, uuid){
return array.filter(item => item.uuid !== uuid);
}
Run Code Online (Sandbox Code Playgroud)
现在这些函数是纯函数(或者,我想,如果我错了,请纠正我),但是为了存储项目列表,每次添加或删除项目时,我都需要创建一个新数组。这不仅效率低下,而且我也不确定如何正确实现它。假设我想每次在DOM中按下按钮时向列表中添加一个新项目:
const button = document.querySelector("#button") //button selector
button.addEventListener("click", buttonClicked)
function buttonClicked(){
const name = document.querySelector("#name").value
const newListOfItems = addItem(items, Item, name);
}
Run Code Online (Sandbox Code Playgroud)
这再次不是纯粹的功能,但是还有另一个问题:这将无法正常工作,因为每次调用该函数时,它将使用现有的“ items”数组创建一个新数组,而该数组本身并不会改变(总是一个空数组)。为了解决这个问题,我只能想到两种解决方案:修改原始的“ items”数组或存储对当前items数组的引用,这两种方法都涉及具有某种副作用的函数。
我试图寻找实现此方法的方法,但是没有成功。有什么办法可以使用纯函数来解决此问题?
提前致谢。
模型-视图-控制器模式用于解决您描述的状态问题。我不会写一篇关于 MVC 的冗长文章,而是通过演示进行教学。假设我们正在创建一个简单的任务列表。以下是我们想要的功能:
那么,让我们开始吧。我们将从创建模型开始。我们的模型将是摩尔机:
// The arguments of createModel are the state of the Moore machine.
// |
// v
const createModel = tasks => ({
// addTask and deleteTask are the transition functions of the Moore machine.
// They return new updated Moore machines and are purely functional.
addTask(task) {
if (tasks.includes(task)) return this;
const newTasks = tasks.concat([task]);
return createModel(newTasks);
},
deleteTask(someTask) {
const newTasks = tasks.filter(task => task !== someTask);
return createModel(newTasks);
},
// Getter functions are the outputs of the Moore machine.
// Unlike the above transition functions they can return anything.
get tasks() {
return tasks;
}
});
const initialModel = createModel([]); // initially the task list is empty
Run Code Online (Sandbox Code Playgroud)
接下来,我们将创建视图,它是一个函数,给定模型的输出返回一个 DOM 列表:
// createview is a pure function which takes the model as input.
// It should only use the outputs of the model and not the transition functions.
// You can use libraries such as virtual-dom to make this more efficient.
const createView = ({ tasks }) => {
const input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("id", "newTask");
const button = document.createElement("input");
button.setAttribute("type", "button");
button.setAttribute("value", "Add Task");
button.setAttribute("id", "addTask");
const list = document.createElement("ul");
for (const task of tasks) {
const item = document.createElement("li");
const span = document.createElement("span");
span.textContent = task;
const remove = document.createElement("input");
remove.setAttribute("type", "button");
remove.setAttribute("value", "Delete Task");
remove.setAttribute("class", "remove");
remove.setAttribute("data-task", task);
item.appendChild(span);
item.appendChild(remove);
list.appendChild(item);
}
return [input, button, list];
};
Run Code Online (Sandbox Code Playgroud)
最后,我们创建连接模型和视图的控制器:
const controller = model => {
const app = document.getElementById("app"); // the place we'll display our app
while (app.firstChild) app.removeChild(app.firstChild); // remove all children
for (const element of createView(model)) app.appendChild(element);
const newTask = app.querySelector("#newTask");
const addTask = app.querySelector("#addTask");
const buttons = app.querySelectorAll(".remove");
addTask.addEventListener("click", () => {
const task = newTask.value;
if (task === "") return;
const newModel = model.addTask(task);
controller(newModel);
});
for (const button of buttons) {
button.addEventListener("click", () => {
const task = button.getAttribute("data-task");
const newModel = model.deleteTask(task);
controller(newModel);
});
}
};
controller(initialModel); // start the app
Run Code Online (Sandbox Code Playgroud)
把它们放在一起:
// The arguments of createModel are the state of the Moore machine.
// |
// v
const createModel = tasks => ({
// addTask and deleteTask are the transition functions of the Moore machine.
// They return new updated Moore machines and are purely functional.
addTask(task) {
if (tasks.includes(task)) return this;
const newTasks = tasks.concat([task]);
return createModel(newTasks);
},
deleteTask(someTask) {
const newTasks = tasks.filter(task => task !== someTask);
return createModel(newTasks);
},
// Getter functions are the outputs of the Moore machine.
// Unlike the above transition functions they can return anything.
get tasks() {
return tasks;
}
});
const initialModel = createModel([]); // initially the task list is empty
// createview is a pure function which takes the model as input.
// It should only use the outputs of the model and not the transition functions.
// You can use libraries such as virtual-dom to make this more efficient.
const createView = ({ tasks }) => {
const input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("id", "newTask");
const button = document.createElement("input");
button.setAttribute("type", "button");
button.setAttribute("value", "Add Task");
button.setAttribute("id", "addTask");
const list = document.createElement("ul");
for (const task of tasks) {
const item = document.createElement("li");
const span = document.createElement("span");
span.textContent = task;
const remove = document.createElement("input");
remove.setAttribute("type", "button");
remove.setAttribute("value", "Delete Task");
remove.setAttribute("class", "remove");
remove.setAttribute("data-task", task);
item.appendChild(span);
item.appendChild(remove);
list.appendChild(item);
}
return [input, button, list];
};
const controller = model => {
const app = document.getElementById("app"); // the place we'll display our app
while (app.firstChild) app.removeChild(app.firstChild); // remove all children
for (const element of createView(model)) app.appendChild(element);
const newTask = app.querySelector("#newTask");
const addTask = app.querySelector("#addTask");
const buttons = app.querySelectorAll(".remove");
addTask.addEventListener("click", () => {
const task = newTask.value;
if (task === "") return;
const newModel = model.addTask(task);
controller(newModel);
});
for (const button of buttons) {
button.addEventListener("click", () => {
const task = button.getAttribute("data-task");
const newModel = model.deleteTask(task);
controller(newModel);
});
}
};
controller(initialModel); // start the appRun Code Online (Sandbox Code Playgroud)
<div id="app"></div>Run Code Online (Sandbox Code Playgroud)
当然,这不是很高效,因为每次更新模型时都会更新整个 DOM。但是,您可以使用virtual-dom等库来解决这个问题。
您还可以查看React和Redux。然而,我不太喜欢它,因为:
然而,它经过了充分的测试并得到了 Facebook 的支持。因此,值得一看。
| 归档时间: |
|
| 查看次数: |
137 次 |
| 最近记录: |