开始使用Recoil作为React应用程序的状态管理库

218 阅读10分钟

作为React应用的状态管理库,Recoil的入门教程

在你的Web应用中管理状态是至关重要的,因为它有助于确保不同页面上显示的数据是一致的。React默认提供了useState() 钩子,我们可以用它来存储和修改应用程序的状态。

这个钩子的缺点是,我们必须把状态从一个组件传递到另一个组件,随着项目变得越来越广泛,传递太多的道具可能会变得很忙。

状态管理库通过创建一个全局存储来解决这个问题,每个组件都可以访问它需要的数据。有了状态管理库,数据从你的应用程序流向状态,反之亦然,你可以从任何组件访问数据,而不需要将其作为道具传递。

我们将通过步骤来使用Recoil来管理我们的React Web应用程序的状态。然后我们将创建一个Todo应用程序,允许用户添加任务,将其标记为完成,删除它们,并在已完成和未完成的任务之间进行过滤。

前提条件

需要以下条件才能跟上。

  • React的中级知识,一个用于构建用户界面的JavaScript库。
  • 了解React功能组件和useState 钩子。
  • 一个代码编辑器和一个浏览器。

目标

在本文结束时,你应该能够。

  • 使用Recoil在你的React应用中管理状态。
  • 对Recoil的原子和选择器有所了解。

开始学习

在我们开始之前,我们需要熟悉以下术语。

  • 一个atom - 原子是一块状态。我们可以将一个原子导入我们的组件中,这样我们就可以从我们的组件中使用和更新它。

每次对原子进行更新时,使用该原子的组件就会以更新的值重新显示。

一个原子的创建如下图所示。

const atomName = atom({
  key: 'atomName',
  default: <defaultValue>,
});

每个原子都应该有一个必须是唯一的键和一个默认值。

  • 一个selector - 一个选择器是一个函数。像普通函数一样,它接受输入(在这里是指其他选择器和原子)并给出输出。它被用来获取基于一个状态的衍生数据。

选择器的定义如下所示。

const selectorName = selector({
  key: 'selectorName',
  get: ({get}) => {
    // code goes in here
  },
});

每个选择器也有一个唯一的键和一个get 属性,即要计算的函数。要访问选择器的输入值,需要使用get 关键字。

创建我们的项目

要创建我们的应用程序,cd 到你想创建项目的目录,在终端运行以下命令。

npx create-react-app react-recoil-todoapp

cd react-recoil-todoapp

code .

这将创建一个React应用程序。继续并在你的代码编辑器中打开它。

接下来,打开终端并运行以下命令,将recoil作为一个依赖项安装。

npm install recoil

创建recoil文件夹

src 文件夹中创建一个新的文件夹,并将其命名为recoil 。在该文件夹中,创建一个新的文件,并将其称为atom.js 。我们将在这个文件中创建我们的原子和选择器。

将下面给出的代码输入到atom.js 文件中。

//import atom and selctor from the recoil package through object destructuring
import { atom, selector } from "recoil";

//create an atom that will be used to store all tasks entered by the user.
const allTasks = atom({
    key: "allTasks",
    default: []
})

//create an atom that will be used to toggle between different values in the filtered tasks selector
const tasksFilter = atom({
    key: "tasksFilter",
    default: "Show All"
  });
 
 //create a selector to help in toggling between all, completed and uncompleted tasks
const filteredTasks = selector({
   key: "filteredTasks",
    get: ({ get }) => {
      const filter = get(tasksFilter);
      const list = get(allTasks);
  
      switch (filter) {
        case "Show Completed":
          return list.filter((item) => item.isDone);
        case "Show Uncompleted":
          return list.filter((item) => !item.isDone);
        default:
          return list;
      }
    }
  });


//export our atoms and selector
export {
    allTasks,
    tasksFilter,
    filteredTasks
}

在上面的代码中,我们首先从recoil导入原子和选择器。然后我们创建我们的allTasks 原子,它将存储用户添加的任务。

我们创建tasksFilter 原子,在过滤所有已完成和未完成的任务时,它将帮助filteredTasks 选择器。然后,我们创建一个filteredTasks 选择器,访问我们的原子并将它们存储在常量中(filterlist )。

然后,它使用switch 语句来返回一个基于所选标准的任务列表。在我们的例子中,完成的、未完成的和所有的(所有的由默认值表示,它返回整个任务的列表)。

创建我们的组件

为了创建我们的组件,在src 文件夹中,创建一个新的文件夹并命名为components

在这个组件文件夹中,创建四个文件,即。

  • Input.js - 这将包含我们将用于添加新任务的表格。
  • Tasks.js - 这将包含一个所有任务的列表。
  • Task.js - 这将代表每个任务。
  • TaskFilters.js - 这将包含一个下拉菜单,我们将用它来选择所有已完成和未完成的任务。

创建我们的todo应用程序

现在我们已经设置好了一切,打开App.css 文件并粘贴下面的代码以应用基本的风格。

.App{
  flex-direction: column;
  display: flex;
  align-items: center;
  justify-content: center;
}

接下来,打开App.js 文件并粘贴下面的代码。

import { RecoilRoot } from "recoil";
import './App.css';

import Input from "./components/Input";
import TaskFilters from "./components/TaskFilters";
import Tasks from "./components/Tasks";

function App() {
  return (
    <div className="App">
      <h2>Todo App with React and Recoil</h2>
      <RecoilRoot>
        <TaskFilters />
        <Input />
        <Tasks />
      </RecoilRoot>
    </div>
  );
}

export default App;

我们导入RecoilRoot ,并在上面的代码中把我们的整个应用程序(需要访问状态的组件)包裹起来。它作为一个提供者,使我们所有的组件可以访问原子和选择器。

在添加任务功能方面的工作

Input.js 文件中,使用下面的代码。这将有助于向我们的原子添加新任务。

import React, { useState } from 'react'
import { useSetRecoilState } from 'recoil'
import { allTasks } from '../recoil/atom'

const Input = () => {
    //Track the value of the input field
    const [input, setInput] = useState("")
    //Use the useSetRecoilState hook to update the allTasks atom
    const setTasks = useSetRecoilState(allTasks);

    //function to be called on the click of the add button.
    const addTask = (e) => {
        //prevent default form behavior on the click of add button
        e.preventDefault();
        //update the allTasks atom with the contents of the input field 
        setTasks((oldTasks) => [
            ...oldTasks, {
                id: Math.floor(Math.random() * 1000), //generate a random id for the new task
                text: input,
                isDone: false //set task completion to false by default
            }
        ])
        setInput("") //clear the contents of the input field
    }

    return (
        <div>
            {/* create a form that will be used to add a task */}
            <form>
                <input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
                {/* Disable the button when the input field is empty */}
                <button type='submit' disabled={!input} onClick={(e) => addTask(e)}>Add</button>
            </form>
        </div>
    )
}

export default Input

在上面的代码中,我们导入了ReactuseState 钩子,这将有助于我们跟踪用户输入的值。我们导入了 RecoiluseSetRecoilState 钩子,这将允许我们向我们的allTasks 原子添加任务,我们也在文件中导入了这个钩子。

我们还创建了一个常量setTasks = useSetRecoilState(allTasks); ,它给了我们一个函数,我们可以用它来修改我们的allTasks 原子。在我们的return 部分,我们有一个form ,其中包含一个输入字段和一个按钮,当点击时调用addTask() 函数。

addTask() 函数使用我们先前创建的setTasks 函数来向我们的原子添加新项目。setTasks 函数接收我们原子中先前包含的项目的值,并返回一个包含新添加项目的数组。

从原子中读取数据

使用下面Tasks.js 中提供的代码。

import React from 'react'
import { useRecoilValue } from "recoil";
import { filteredTasks } from '../recoil/atom';
import Task from './Task';

function Tasks() {
    //read the default return value(which is a list of all tasks) of the filtered tasks selector and assign it to a constant tasks
    const tasks = useRecoilValue(filteredTasks);
    return (
        <div>
            //map through the tasks array and call the Task component for each element. Also pass the value of each element to the Task component
            {tasks.map((task, index) => (
                <Task task={task} key={index} />
            ))}
        </div>
    )
}
export default Tasks

在上面的代码中,我们已经导入了useRecoilValue ,以便从原子中读取数据。此外,我们还导入了包含我们想要访问的数据的filteredTasks 选择器。

使用const tasks = useRecoilValue(filteredTasks); ,我们可以读取filteredTasks 选择器中包含的数据,并将其存储在一个名为tasks 的常量中。filteredTasks 选择器的默认返回值列出了所有任务。

return 部分,我们已经通过tasks 数组进行了映射,并将数据传递给我们的Task 组件。

接下来,打开Task.js 文件,粘贴下面的代码。

import React from 'react'
import { useRecoilState } from 'recoil';
import { allTasks } from '../recoil/atom';

const Task = ({ task }) => {
    const [tasks, setTasks] = useRecoilState(allTasks);
    const index = tasks.findIndex((taskItem) => taskItem === task);

    const replaceItemAtIndex = (arr, index, newValue) => {
        return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
    };

    const removeItemAtIndex = (arr, index) => {
        return [...arr.slice(0, index), ...arr.slice(index + 1)];
    };

    const toggleTaskCompletion = () => {
        const newTasks = replaceItemAtIndex(tasks, index, {
          ...task,
          isDone: !task.isDone
        });
        setTasks(newTasks)
    };

    //Delete a task
    const deleteTask = (id) => {
        const newTasks = removeItemAtIndex(tasks, index);

        setTasks(newTasks);
    }

    return (
        <div>
            <span>{task.text}</span>
            <input
                type="checkbox"
                checked={task.isDone}
                onChange={toggleTaskCompletion}
            />
            <button onClick={deleteTask}>X</button>
        </div>
    )
}

export default Task

这个组件接受从Tasks.js 文件中通过对象析构传递的道具。使用传来的道具,我们返回一个包含文本的span ,一个我们可以用来切换项目完成情况的复选框,以及一个我们可以用来删除任务的按钮。

复选框的检查值是基于任务被标记为完成或未完成。在点击复选框的时候,我们要调用toggleTaskCompletion() 函数。

toggleTaskCompletion() 调用replaceItemAtIndex() ,并传递任务数组、被点击的项目的索引以及被点击的项目本身(该项目使用spread运算符铺开,以便其内容可以被修改。展开后,isDone 的当前值被附加)作为参数。

我们从这个函数中得到项目的索引,const index = tasks.findIndex((taskItem) => taskItem === task)replaceItemAtIndex() 函数接收传给它的参数,并返回一个用slice方法修改的数组。

toggleTaskCompletion() 将从replaceItemAtIndex() 函数收到的数组存储在名为newTasks 的常量中。然后它调用setTasks() 函数并传递给newTasks

setTasks() 函数用新的数组更新我们的原子。随着删除按钮的点击,我们正在调用deleteTask() 方法。该方法调用removeItemAtIndex() 函数并传递任务数组和被点击的项目索引作为参数。

removeItemAtIndex() 接收传递给它的参数,并返回一个使用JavaScript的slice() 方法修改的数组。deleteTask() 将返回的数组存储在一个名为newTasks 的常量中。然后它调用setTasks() 函数并传递newTasks,更新我们的allTasks 原子。

实现过滤器

我们现在可以添加、删除和切换项目完成,我们可以实现过滤器,这将帮助我们根据特定的标准显示任务。

要做到这一点,打开TaskFilters.js 文件并粘贴下面的代码。

import React from 'react'
import { useRecoilState } from "recoil";
import { tasksFilter } from '../recoil/atom';

const TaskFilters = () => {

    const [filter, setFilter] = useRecoilState(tasksFilter);

    return (
        <div>
            Filter:
                <select value={filter} onChange={(e) => setFilter(e.target.value)}>
                    <option value="Show All">All</option>
                    <option value="Show Completed">Completed</option>
                    <option value="Show Uncompleted">Uncompleted</option>
                </select>
        </div>
    )
}

export default TaskFilters

在上面的代码中,我们有一个有三个选项的下拉菜单。在改变选项时,我们将当前选择的选项的值传递给引用tasksFilter 原子的setFilter() 函数。

在传递过滤器时,filteredTasks 选择器中的开关语句将过滤器与定义的案例相匹配,并只返回符合预定义标准的任务。

运行我们的应用程序

要运行该应用程序,打开集成终端并运行以下命令。

npm start

在你的浏览器上,打开链接localhost:3000, 。你可以添加任务,删除任务,将其标记为完成或未完成,并在已完成、未完成和所有任务之间进行过滤。

结论

这是在我们的React应用中对Recoil的一个基本实现。在更好地理解上面讨论的概念后,你可以在你的项目中实现Recoil。

你也可以在这个项目上下功夫,把它提升到更高的水平。