react本地储存

by Edward Njoroge

爱德华·尼约格(Edward Njoroge)

如何使用React和本地存储构建freeCodeCamp的配方框 (How to build freeCodeCamp’s recipe box using React and local storage)

I completed my first edition of the Free Code Camp recipe box project on May 3, 2018. I put it up here for review. Then I didn’t check the reviews for a few weeks. When I returned, I was shocked to learn that I had overlooked an important feature in forms.

我于2018年5月3日完成了第一版的Free Code Camp食谱盒项目。我将其放在此处进行审核。 然后,我有几个星期没有检查评论了。 当我回来时,得知自己忽略了表格中的一个重要功能,我感到震惊。

I know. Terrible mistake. My form allowed for the creation of an empty recipe. This oversight shows the importance of allowing other people to review your code.

我知道。 可怕的错误。 我的表格允许创建一个空配方。 这种疏忽表明允许其他人查看您的代码的重要性。

It turned out I wasn’t the only one that missed this important feature. I checked freeCodeCamp’s example project for the recipe box (here) and it was missing the same feature. Validation is not mentioned in the user stories (here) either.

事实证明,我并不是唯一一个错过这一重要功能的人。 我检查了freeCodeCamp的示例项目中的配方框( 在此处 ),它缺少相同的功能。 用户故事( 此处 )中也未提及验证。

I figured that if I included validation in my project, I could try to convince freeCodeCamp to make my recipe box the example project for this challenge. So I restarted the project, and during this process I was inspired to write this Medium post.

我认为,如果我在项目中包含验证,则可以尝试说服freeCodeCamp使我的配方盒成为应对这一挑战的示例项目。 因此,我重新启动了该项目,在此过程中,我受到启发而撰写了这篇Medium帖子。

建立配方盒 (Building the recipe box)

For this project, we will use create-react-app, React bootstrap, and bootstrap CSS.

对于此项目,我们将使用create-react-app,React引导程序和引导CSS。

步骤1:设置React环境并添加React引导程序。 (Step 1: Set up the React environment and add React bootstrap.)

npx create-react-app recipe-box
npm install react-bootstrap --save

We will create a file directory that resembles the one below:

我们将创建一个类似于以下内容的文件目录:

We delete favicon.ico and manifest.json from the public folder, and everything except index.js and index.css from the src folder. Inside the SRC folder, create a components folder and a CSS folder. Move index.css to the CSS folder.

我们从公用文件夹中删除favicon.ico和manifest.json,并从src文件夹中删除除index.js和index.css之外的所有内容。 在SRC文件夹中,创建一个components文件夹和一个CSS文件夹。 将index.css移到CSS文件夹。

步骤2:在index.html中设置html。 (Step 2: Set up the html in index.html.)

In index.html:

在index.html中:

<!DOCTYPE html>
<html lang="en"><head><title>Recipe Box</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="This is a Free Code Camp Project called Recipe Box"><meta name="keywords" content="HTML, CSS, JAVASCRIPT, REACTJS"><meta name="author" content="Your Name"><link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"><link href="https://fonts.googleapis.com/css?family=Mina" rel="stylesheet"></head><body><!--set up a div where all the code will be rendered--><div class="container" id="app"></div></body>
</html>

步骤3:设置配方框的第一个视图。 (Step 3: Set up the first view of the recipe box.)

In index.js, we create an initial list of recipes in this.state and display them.

在index.js中,我们在this.state中创建配方的初始列表并显示它们。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {constructor(props) {super(props);this.state = {recipes: [{name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},{name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},{name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}]};}render() {const recipes = this.state.recipes;return(<div className="jumbotron"><h1>RECIPE BOX</h1><PanelGroup accordion id="recipes">{recipes.map((recipe, index) => (<Panel eventKey={index} key={index}><Panel.Heading><Panel.Title className="title" toggle>{recipe.name}</Panel.Title></Panel.Heading><Panel.Body collapsible><ListGroup>{recipe.ingredients.map((ingredient, index) => (<ListGroupItem key={index}>{ingredient}</ListGroupItem>))}</ListGroup><ButtonToolbar><Button bsStyle="warning">Edit</Button><Button bsStyle="danger">Delete</Button></ButtonToolbar></Panel.Body></Panel>))}</PanelGroup><Button bsStyle="primary">Add Recipe</Button></div>);}
};ReactDOM.render(<Recipe />, document.getElementById('app'));

In index.css:

在index.css中:

h1, li, .title {font-family: 'Mina';
}
h1, li {text-align: center;
}
.title {background-color: #D8BFD8;font-size: 20px;
}
li {list-style-type: none;font-size: 18px;
}

Result:

结果:

步骤4:创建添加配方功能。 (Step 4: Creating the Add Recipe function.)

We are now ready to add recipes. We create a file called addrecipe.js inside the components folder.

我们现在准备添加配方。 我们在components文件夹中创建一个名为addrecipe.js的文件。

Recipes will be added through a modal form. We must first be able to activate and deactivate the modal. We create a state called showAdd and set it to false. Then we create a function called showAddModal() that changes showAdd to true if its currently false and vice versa.

食谱将以模态形式添加。 我们必须首先能够激活和停用模式。 我们创建一个名为showAdd的状态并将其设置为false。 然后,我们创建一个名为showAddModal()的函数,如果showAdd当前为false,则将showAdd更改为true,反之亦然。

When the “Add Recipe” button is clicked, showAdd will turn to true and the modal will be displayed. Therefore, showAdd and showAddModal() must be passed as props to addrecipe.js.

单击“添加配方”按钮时,showAdd将变为true,并显示模态。 因此,必须将showAdd和showAddModal()作为props传递给addrecipe.js。

To add a recipe, an addRecipe() function that takes the argument ‘recipe’ will be created. It takes the details for the new recipe, and pushes them to the end of the recipe state array. This function will also be passed as a prop to addrecipe.js.

要添加配方,将创建一个带有参数“ recipe”的addRecipe()函数。 它获取新配方的详细信息,并将其推送到配方状态数组的末尾。 此函数还将作为propre传递给addrecipe.js。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import {AddRecipe} from './components/addrecipe';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {constructor(props) {super(props);this.state = {recipes: [{name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},{name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},{name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}],showAdd: false};this.showAddModal = this.showAddModal.bind(this);this.addRecipe = this.addRecipe.bind(this);}showAddModal() {//show the new recipe modalthis.setState({showAdd: !this.state.showAdd});}addRecipe(recipe) {//create a new recipelet recipes = this.state.recipes;recipes.push(recipe);this.setState({recipes: recipes});this.showAddModal();}render() {const recipes = this.state.recipes;return(<div className="jumbotron"><h1>RECIPE BOX</h1><PanelGroup accordion id="recipes">{recipes.map((recipe, index) => (<Panel eventKey={index} key={index}><Panel.Heading><Panel.Title className="title" toggle>{recipe.name}</Panel.Title></Panel.Heading><Panel.Body collapsible><ListGroup>{recipe.ingredients.map((ingredient, index) => (<ListGroupItem key={index}>{ingredient}</ListGroupItem>))}</ListGroup><ButtonToolbar><Button bsStyle="warning">Edit</Button><Button bsStyle="danger">Delete</Button></ButtonToolbar></Panel.Body></Panel>))}</PanelGroup><Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button><AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} /></div>);}
};ReactDOM.render(<Recipe />, document.getElementById('app'));
ReactDOM.render(<Recipe />, document.getElementById('app'));

In addrecipe.js, we create a state that holds the new recipe name and recipe ingredients, and the initial values are empty strings. We will then change the state every time we change the contents of the form as we would in a markdown. This will make form validation easier.

在addrecipe.js中,我们创建一个状态,其中包含新的配方名称和配方成分,并且初始值为空字符串。 然后,我们每次更改表单内容时都将更改状态,就像在markdown中一样。 这将使表单验证更加容易。

Instead of displaying form errors for validation, we use regular expression to ensure that we only save a recipe if some conditions are met. These conditions are:

我们使用正则表达式来确保仅在满足某些条件的情况下保存配方,而不是显示用于验证的表单错误。 这些条件是:

(a) Both the recipe name and ingredients sections must not be empty, that is both must have at least one character.

(a)食谱名称和配料部分都不能为空,即都必须至少包含一个字符。

(b) The form recipe name cannot begin with a space. This ensures that the recipe name begins with at least one alphanumeric character or symbol.

(b)表单配方名称不能以空格开头。 这样可以确保配方名称至少以一个字母数字字符或符号开头。

(c)The form recipe ingredients cannot begin or end with a space or comma. This is because ingredients will be split by commas into an array that is then displayed as a list like our current ingredients are.

(c)配方成分不能以空格或逗号开头或结尾。 这是因为成分将通过逗号分隔成一个数组,然后像我们当前的成分一样显示为列表。

The modal will have a Save Recipe button which will be disabled until all conditions are met. When save recipe is clicked, the recipe will be added to our recipe box.

该模态将具有一个“保存配方”按钮,该按钮将被禁用,直到满足所有条件为止。 单击保存配方后,该配方将添加到我们的配方框中。

In addrecipe.js:

在addrecipe.js中:

//import the necessary files
import React from 'react';
import {Modal,ControlLabel,FormGroup,FormControl,Button} from 'react-bootstrap';//create a class for displaying the modal for adding a new recipe and export it
export class AddRecipe extends React.Component {constructor(props) {//create a state to handle the new recipesuper(props);this.state = {name: "", ingredients: ""};this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this);this.handleRecipeIngredientsChange = this.handleRecipeIngredientsChange.bind(this);this.handleSubmit = this.handleSubmit.bind(this);this.handleCancel = this.handleCancel.bind(this);}handleRecipeNameChange(e) {//change the name to reflect user inputthis.setState({name: e.target.value});}handleRecipeIngredientsChange(e) {//change the ingredients to reflect user inputthis.setState({ingredients: e.target.value});}handleSubmit(e) {//get the recipe data, manipulate it and call the function for creating a new recipee.preventDefault();const onAdd = this.props.onAdd;const regExp = /\s*,\s*/;var newName = this.state.name;var newIngredients = this.state.ingredients.split(regExp);var newRecipe = {name: newName, ingredients: newIngredients};onAdd(newRecipe);this.setState({name: "", ingredients: ""});}handleCancel() {const onAddModal = this.props.onAddModal;this.setState({name: "", ingredients: ""});onAddModal();}render() {const onShow = this.props.onShow;var regex1 = /^\S/;var regex2 = /^[^,\s]/;var regex3 = /[^,\s]$/;const validRecipe = regex1.test(this.state.name) && regex2.test(this.state.ingredients) && regex3.test(this.state.ingredients);return(<Modal show={onShow} onHide={this.handleCancel}><Modal.Header closeButton><Modal.Title>New Recipe</Modal.Title></Modal.Header><Modal.Body><FormGroup controlId="formControlsName"><ControlLabel>Recipe Name</ControlLabel><FormControl type="text" required onChange={this.handleRecipeNameChange} value={this.state.name} placeholder="Enter Name" /></FormGroup><FormGroup controlId="formControlsIngredients"><ControlLabel>Recipe Ingredients</ControlLabel><FormControl componentClass="textarea" type="text" required onChange={this.handleRecipeIngredientsChange} value={this.state.ingredients} placeholder="Enter Ingredients(separate by commas)" /></FormGroup></Modal.Body><Modal.Footer><Button disabled={!validRecipe} bsStyle="success" onClick={this.handleSubmit}>Save Recipe</Button></Modal.Footer></Modal>);}
};

Result:

结果:

步骤5:创建“编辑配方”功能。 (Step 5: Creating the Edit Recipe function.)

We are now ready to edit recipes. We create a file called editrecipe.js inside the components folder.

现在我们可以编辑配方了。 我们在components文件夹内创建一个名为editrecipe.js的文件。

Recipes will be edited through a modal form. We must first be able to activate and deactivate the modal. We create a state called showEdit and set it to false. Then we create a function called showEditModal() that changes showEdit to true if its currently false and vice versa. When the “Edit” button is clicked, showEditModal() will run, showEdit will turn to true, and the modal will be displayed.

配方将通过模式表格进行编辑。 我们必须首先能够激活和停用模态。 我们创建一个名为showEdit的状态并将其设置为false。 然后,我们创建一个名为showEditModal()的函数,如果showEdit当前为false,则将showEdit更改为true,反之亦然。 单击“编辑”按钮后,将运行showEditModal(),showEdit将变为true,并显示模式。

We will also need a way to ensure that the correct recipe is displayed on the form fields for editing. We create a state called currentlyEditing and set it to 0. We then ensure that the details of this.state.recipes[currentlyEditing] are displayed on the form.

我们还需要一种方法来确保在表单字段上显示正确的配方以进行编辑。 我们创建一个名为currentlyEditing的状态并将其设置为0。然后确保this.state.recipes [currentlyEditing]的详细信息显示在表单上。

Since 0 is the default, whenever Edit Recipe is clicked, the form will only show the details of the first recipe. We need a way to update currentlyEditing to the index of the recipe we want displayed.

由于默认值为0,因此每当单击“编辑配方”时,表格将仅显示第一个配方的详细信息。 我们需要一种方法来将currentEditing更新为要显示的配方的索引。

In showEditModal(), we pass index as an argument and this argument will be equal to the index of the current recipe. Now when the “Edit Recipe” button is clicked, showEditModal() will run, showEdit will turn to true, currentlyEditing will become the index of the recipe, and the modal will be displayed with the correct recipe’s information. Therefore, showEdit and showEditModal(index) must be passed as props to editrecipe.js.

在showEditModal()中,我们将index作为参数传递,并且该参数将等于当前配方的索引。 现在,当单击“编辑配方”按钮时,showEditModal()将运行,showEdit将变为true,当前编辑将成为配方的索引,并且模态将显示正确的配方信息。 因此,必须将showEdit和showEditModal(index)作为props传递给editrecipe.js。

To edit a recipe, an editRecipe() function that takes the arguments newName, newIngredients, and currentlyEditing will be created. In this function, we use currentlyEditing (which is now the index of the recipe we are editing) to identify that recipe and set its name to the newName and its ingredients to the newIngredients. Therefore, editRecipe, the recipe we need to edit, and currentlyEditing must be passed as props to editrecipe.js.

要编辑配方,将创建一个带有参数newName,newIngredients和currentEditing的editRecipe()函数。 在此功能中,我们使用currentlyEditing(现在是我们正在编辑的配方的索引)来标识该配方,并将其名称设置为newName,并将其成分设置为newIngredients。 因此,必须将editRecipe,我们需要编辑的配方以及当前编辑作为道具传递给editrecipe.js。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import {AddRecipe} from './components/addrecipe';
import {EditRecipe} from './components/editrecipe';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {constructor(props) {super(props);this.state = {recipes: [{name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},{name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},{name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}],showAdd: false,showEdit: false,currentlyEditing: 0};this.showAddModal = this.showAddModal.bind(this);this.showEditModal = this.showEditModal.bind(this);this.addRecipe = this.addRecipe.bind(this);this.editRecipe = this.editRecipe.bind(this);}showAddModal() {//show the new recipe modalthis.setState({showAdd: !this.state.showAdd});}showEditModal(index) {//show the edit recipe modalthis.setState({showEdit: !this.state.showEdit, currentlyEditing: index});}addRecipe(recipe) {//create a new recipelet recipes = this.state.recipes;recipes.push(recipe);this.setState({recipes: recipes});this.showAddModal();}editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipelet recipes = this.state.recipes;recipes[currentlyEditing] = {name: newName, ingredients: newIngredients};this.setState({recipes: recipes});this.showEditModal(currentlyEditing);}render() {const recipes = this.state.recipes;return(<div className="jumbotron"><h1>RECIPE BOX</h1><PanelGroup accordion id="recipes">{recipes.map((recipe, index) => (<Panel eventKey={index} key={index}><Panel.Heading><Panel.Title className="title" toggle>{recipe.name}</Panel.Title></Panel.Heading><Panel.Body collapsible><ListGroup>{recipe.ingredients.map((ingredient, index) => (<ListGroupItem key={index}>{ingredient}</ListGroupItem>))}</ListGroup><ButtonToolbar><Button bsStyle="warning" onClick={() => {this.showEditModal(index)}}>Edit</Button><Button bsStyle="danger">Delete</Button></ButtonToolbar></Panel.Body><EditRecipe onShow={this.state.showEdit} onEdit={this.editRecipe} onEditModal={() => {this.showEditModal(this.state.currentlyEditing)}} currentlyEditing={this.state.currentlyEditing} recipe={recipes[this.state.currentlyEditing]} /></Panel>))}</PanelGroup><Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button><AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} /></div>);}
};ReactDOM.render(<Recipe />, document.getElementById('app'));
ReactDOM.render(<Recipe />, document.getElementById('app'));

In editrecipe.js:

在editrecipe.js中:

//import the necessary files
import React from 'react';
import {Modal,ControlLabel,FormGroup,FormControl,Button} from 'react-bootstrap';//create a class for displaying the modal for editing an existing recipe and export it
export class EditRecipe extends React.Component {constructor(props) {//create a state to handle the recipe to be editedsuper(props);this.state = {name: "", ingredients: ""};this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this);this.handleRecipeIngredientsChange = this.handleRecipeIngredientsChange.bind(this);this.handleEdit = this.handleEdit.bind(this);this.handleCancel = this.handleCancel.bind(this);}static getDerivedStateFromProps(props, state) {//make the recipe prop a stateconst prevName = state.prevName;const prevIngredients = state.prevIngredients;const name = prevName !== props.recipe.name ? props.recipe.name : state.name;const ingredients = prevIngredients !== props.recipe.ingredients.join(",") ? props.recipe.ingredients.join(",") : state.ingredients;return {prevName: props.recipe.name, name,prevIngredients: props.recipe.ingredients.join(","), ingredients,}}handleRecipeNameChange(e) {//change the name to reflect user inputthis.setState({name: e.target.value});}handleRecipeIngredientsChange(e) {//change the ingredients to reflect user inputthis.setState({ingredients: e.target.value});}handleEdit(e) {//get the recipe data, manipulate it and call the function for editing an existing recipee.preventDefault();const onEdit = this.props.onEdit;const currentlyEditing = this.props.currentlyEditing;const regExp = /\s*,\s*/;var name = this.state.name;var ingredients = this.state.ingredients.split(regExp);onEdit(name, ingredients, currentlyEditing);}handleCancel() {const onEditModal = this.props.onEditModal;this.setState({name: this.props.recipe.name, ingredients: this.props.recipe.ingredients.join(",")});onEditModal();}render() {const onShow = this.props.onShow;var regex1 = /^\S/;var regex2 = /^[^,\s]/;var regex3 = /[^,\s]$/;const validRecipe = regex1.test(this.state.name) && regex2.test(this.state.ingredients) && regex3.test(this.state.ingredients);return(<Modal show={onShow} onHide={this.handleCancel}><Modal.Header closeButton><Modal.Title>Edit Recipe</Modal.Title></Modal.Header><Modal.Body><FormGroup controlId="formControlsName"><ControlLabel>Recipe Name</ControlLabel><FormControl type="text" required onChange={this.handleRecipeNameChange} value={this.state.name} placeholder="Enter Name" /></FormGroup><FormGroup controlId="formControlsIngredients"><ControlLabel>Recipe Ingredients</ControlLabel><FormControl componentClass="textarea" type="text" required onChange={this.handleRecipeIngredientsChange} value={this.state.ingredients} placeholder="Enter Ingredients(separate by commas)" /></FormGroup></Modal.Body><Modal.Footer><Button disabled={!validRecipe} bsStyle="success" onClick={this.handleEdit}>Save Recipe</Button></Modal.Footer></Modal>);}
};

Result:

结果:

In editRecipe.js, we create a state that holds the name and ingredients of the recipe to be edited, and set the initial values as empty strings. We then use React’s new life cycle method getDerivedStateFromProps to make our recipe prop’s name and ingredients the new name and ingredients of our state. The method for doing so is clearly explained here.

在editRecipe.js中,我们创建一个状态,其中包含要编辑的配方的名称和成分,并将初始值设置为空字符串。 然后,我们使用React的新生命周期方法getDerivedStateFromProps将配方道具的名称和成分设为状态的新名称和成分。 这样做的方法在这里已明确说明。

We will then change the state every time we change the contents of the form and validate the form as we did when adding a new recipe.

然后,我们将在每次更改表单内容时更改状态,并像添加新配方时一样对表单进行验证。

步骤6:创建Delete Recipe功能。 (Step 6: Creating the Delete Recipe function.)

We are now ready to delete recipes. This step does not need the creation of a new file.

现在我们准备删除配方。 此步骤不需要创建新文件。

To delete a recipe, a deleteRecipe() function that takes the argument index will be created. In this function, we use the index of a recipe to identify the recipe to be deleted. We will use JavaScript’s splice method to delete the recipe. We then set currentlyEditing to 0 just to reset the recipe box, that is, we don’t want currentlyEditing to still be the index of a recipe that doesn’t exist anymore.

要删除配方,将创建一个使用参数索引的deleteRecipe()函数。 在此功能中,我们使用配方的索引来标识要删除的配方。 我们将使用JavaScript的splice方法删除配方。 然后,我们将currentEditing设置为0只是为了重置配方框,也就是说,我们不希望currentEditing仍然是不再存在的配方的索引。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import {AddRecipe} from './components/addrecipe';
import {EditRecipe} from './components/editrecipe';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {constructor(props) {super(props);this.state = {recipes: [{name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},{name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},{name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}],showAdd: false,showEdit: false,currentlyEditing: 0};this.showAddModal = this.showAddModal.bind(this);this.showEditModal = this.showEditModal.bind(this);this.addRecipe = this.addRecipe.bind(this);this.editRecipe = this.editRecipe.bind(this);this.deleteRecipe = this.deleteRecipe.bind(this);}showAddModal() {//show the new recipe modalthis.setState({showAdd: !this.state.showAdd});}showEditModal(index) {//show the edit recipe modalthis.setState({showEdit: !this.state.showEdit, currentlyEditing: index});}addRecipe(recipe) {//create a new recipelet recipes = this.state.recipes;recipes.push(recipe);this.setState({recipes: recipes});this.showAddModal();}editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipelet recipes = this.state.recipes;recipes[currentlyEditing] = {name: newName, ingredients: newIngredients};this.setState({recipes: recipes});this.showEditModal(currentlyEditing);}deleteRecipe(index) {//delete an existing recipelet recipes = this.state.recipes.slice();recipes.splice(index, 1);this.setState({recipes: recipes, currentlyEditing: 0});}render() {const recipes = this.state.recipes;return(<div className="jumbotron"><h1>RECIPE BOX</h1><PanelGroup accordion id="recipes">{recipes.map((recipe, index) => (<Panel eventKey={index} key={index}><Panel.Heading><Panel.Title className="title" toggle>{recipe.name}</Panel.Title></Panel.Heading><Panel.Body collapsible><ListGroup>{recipe.ingredients.map((ingredient, index) => (<ListGroupItem key={index}>{ingredient}</ListGroupItem>))}</ListGroup><ButtonToolbar><Button bsStyle="warning" onClick={() => {this.showEditModal(index)}}>Edit</Button><Button bsStyle="danger" onClick={() => {this.deleteRecipe(index)}}>Delete</Button></ButtonToolbar></Panel.Body><EditRecipe onShow={this.state.showEdit} onEdit={this.editRecipe} onEditModal={() => {this.showEditModal(this.state.currentlyEditing)}} currentlyEditing={this.state.currentlyEditing} recipe={recipes[this.state.currentlyEditing]} /></Panel>))}</PanelGroup><Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button><AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} /></div>);}
};ReactDOM.render(<Recipe />, document.getElementById('app'));

Result:

结果:

步骤7:添加本地存储。 (Step 7: Adding Local Storage.)

HTML 5 Web Storage allows web applications to store data locally within the user’s browser. There are two web storage objects:

HTML 5 Web存储允许Web应用程序在用户的浏览器中本地存储数据。 有两个Web存储对象:

(a) Session Storage: Session storage stores data for one session, and the data is lost when the browser tab is closed.

(a)会话存储:会话存储存储一个会话的数据,并且在关闭浏览器选项卡时数据会丢失。

(b) Local Storage: Local storage stores data indefinitely. The data will not be deleted when the browser is closed, and will be available all the time since there is no expiration date.

(b)本地存储:本地存储无限期地存储数据。 当浏览器关闭时,该数据将不会被删除,并且由于没有到期日期,因此将一直可用。

To add local storage, we will change our recipe state to an empty array. We will get the recipes from local storage first and then set our recipe state to these recipes. We will use the life cycle method componentDidMount, because we want to load the local storage after our component renders. We will also be updating local storage whenever we add, edit, or delete a recipe.

要添加本地存储,我们将配方状态更改为空数组。 我们将首先从本地存储中获取食谱,然后将我们的食谱状态设置为这些食谱。 我们将使用生命周期方法componentDidMount,因为我们想在组件渲染后加载本地存储。 每当我们添加,编辑或删除配方时,我们还将更新本地存储。

So if, for example, we delete one of our original 3 recipes and reload the page, we will not see the recipe we deleted. When we clear our local storage and reload the page, we will once again see the original recipe we deleted.

因此,例如,如果我们删除原始的3个食谱之一并重新加载页面,则不会看到删除的食谱。 当我们清除本地存储并重新加载页面后,我们将再次看到我们删除的原始配方。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import './css/index.css';
import {AddRecipe} from './components/addrecipe';
import {EditRecipe} from './components/editrecipe';
//create the main class for displaying the recipes
class Recipe extends React.Component {constructor(props) {super(props);this.state = {recipes: [],showAdd: false,showEdit: false,currentlyEditing: 0};this.showAddModal = this.showAddModal.bind(this);this.showEditModal = this.showEditModal.bind(this);this.addRecipe = this.addRecipe.bind(this);this.editRecipe = this.editRecipe.bind(this);this.deleteRecipe = this.deleteRecipe.bind(this);}componentDidMount() {//load the local storage data after the component rendersvar recipes = (typeof localStorage["recipes"] !== "undefined") ? JSON.parse(localStorage.getItem("recipes")) : [{name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},{name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},{name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}];this.setState({recipes: recipes});}showAddModal() {//show the new recipe modalthis.setState({showAdd: !this.state.showAdd});}showEditModal(index) {//show the edit recipe modalthis.setState({currentlyEditing: index, showEdit: !this.state.showEdit});}addRecipe(recipe) {//create a new recipelet recipes = this.state.recipes;recipes.push(recipe);localStorage.setItem('recipes', JSON.stringify(recipes));this.setState({recipes: recipes});this.showAddModal();}editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipelet recipes = this.state.recipes;recipes[currentlyEditing] = {name: newName, ingredients: newIngredients};localStorage.setItem('recipes', JSON.stringify(recipes));this.setState({recipes: recipes});this.showEditModal(currentlyEditing);}deleteRecipe(index) {//delete an existing recipelet recipes = this.state.recipes.slice();recipes.splice(index, 1);localStorage.setItem('recipes', JSON.stringify(recipes));this.setState({recipes: recipes, currentlyEditing: 0});}render() {const recipes = this.state.recipes;var currentlyEditing = this.state.currentlyEditing;return(<div className="jumbotron"><h1>RECIPE BOX</h1><PanelGroup accordion id="recipes">{recipes.map((recipe, index) => (<Panel eventKey={index} key={index}><Panel.Heading><Panel.Title className="title" toggle>{recipe.name}</Panel.Title></Panel.Heading><Panel.Body collapsible><ListGroup>{recipe.ingredients.map((ingredient, index) => (<ListGroupItem key={index}>{ingredient}</ListGroupItem>))}</ListGroup><ButtonToolbar><Button bsStyle="warning" onClick={() => {this.showEditModal(index)}}>Edit</Button><Button bsStyle="danger" onClick={() => {this.deleteRecipe(index)}}>Delete</Button></ButtonToolbar></Panel.Body><EditRecipe onShow={this.state.showEdit} onEdit={this.editRecipe} onEditModal={() => {this.showEditModal(currentlyEditing)}} currentlyEditing={currentlyEditing} recipe={recipes[currentlyEditing]} /></Panel>))}</PanelGroup><Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button><AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} /></div>);}
};ReactDOM.render(<Recipe />, document.getElementById('app'));

Result:

结果:

在GitHub上发布 (Posting on GitHub)

We are done making the recipe box. Time to post it on GitHub and create a GitHub page for it.

我们完成了配方框的制作。 是时候将其发布到GitHub并为其创建GitHub页面了。

On GitHub, create a new repository called recipe-box.

在GitHub上,创建一个新的存储库,称为“秘方盒”。

Go to your file directory on the command line and type the following:

在命令行上转到文件目录,然后键入以下内容:

git init
git add README.md
git commit -m "initial commit"
git remote add origin https://github.com/yourusername/recipe-box.git
git push -u origin master

Your code is now on GitHub. Now its time to create a GitHub page for the repository. This should be the current status of the package.json file:

您的代码现在在GitHub上。 现在该为存储库创建GitHub页面了。 这应该是package.json文件的当前状态:

On the command line we run:

在命令行上,我们运行:

npm install gh-pages --save-dev

GitHub pages will be installed. Then we must specify our “homepage” URL, and predeploy and deploy code in “scripts”, in package.json. The end result should be:

GitHub页面将被安装。 然后,我们必须指定我们的“主页” URL,并在package.json中的“脚本”中预部署和部署代码。 最终结果应为:

On the command line we run:

在命令行上,我们运行:

npm run deploy
git add .
git commit -m "created a github page for the repository"
git push origin master

We now have a GitHub page for the recipe box, and its URL is the one specified in “homepage” of package.json.

现在,我们为配方框提供了一个GitHub页面,其URL是package.json的“主页”中指定的URL。

The project is complete. For reference you can check out my GitHub repository here.

该项目已完成。 作为参考,您可以在此处查看我的GitHub存储库。

结论 (Conclusion)

This was certainly a thrilling challenge to tackle. I enjoyed sharing this with you. I hope you’ve learned something from it.

当然,这是一个艰巨的挑战。 我很高兴与您分享。 希望您从中学到了一些东西。

Thank you for reading.

感谢您的阅读。

翻译自: https://www.freecodecamp.org/news/how-to-build-freecodecamps-recipe-box-using-react-and-local-storage-3f285a96fe44/

react本地储存

react本地储存_如何使用React和本地存储构建freeCodeCamp的配方框相关推荐

  1. react本地储存_如何在React项目中利用本地存储

    react本地储存 以及为什么要这么做. 本地存储是现代Web浏览器固有的Web API. 它允许网站/应用程序在浏览器中存储数据(简单和有限),从而使数据在以后的浏览器会话中可用. 在开始学习本教程 ...

  2. react前端项目_如何使用React前端设置Ruby on Rails项目

    react前端项目 The author selected the Electronic Frontier Foundation to receive a donation as part of th ...

  3. react前端开发_是的,React正在接管前端开发。 问题是为什么。

    react前端开发 by Samer Buna 通过Samer Buna 是的,React正在接管前端开发. 问题是为什么. (Yes, React is taking over front-end ...

  4. react实战课程_在使用React一年后,我学到的最重要的课程

    react实战课程 by Tomas Eglinskas 由Tomas Eglinskas 在使用React一年后,我学到的最重要的课程 (The most important lessons I'v ...

  5. react测试组件_如何测试React组件:完整指南

    react测试组件 When I first started learning to test my apps back in the day, I would get very frustrated ...

  6. react java编程_快速上手React编程 PDF 下载

    资料目录: 第1章  初积React  3 1.1  什么是React  4 1.2 React解决的问题  5 1.3  使用React的好处  6 1.3.1  简单性  6 1.3.2  速度和 ...

  7. react jest测试_如何使用React测试库和Jest开始测试React应用

    react jest测试 Testing is often seen as a tedious process. It's extra code you have to write, and in s ...

  8. 创建react应用程序_如何使用React创建一个三层应用程序

    创建react应用程序 Discover Functional JavaScript was named one of the best new Functional Programming book ...

  9. react页面保留_如何在React中保留已登录的用户

    react页面保留 If you run a quick Google search for persisting a logged-in user in React (or keeping a us ...

最新文章

  1. Go 学习笔记(43)— Go 标准库之 os/exec(执行外部命令、非阻塞等待、阻塞等待、命令输出)
  2. 考研计算机专业课怎么复习,2016考研计算机专业课如何复习?
  3. 自动渗透测试工具集APT2
  4. 基于数据挖掘的旅游推荐APP(二):主界面布局
  5. 如何去写一手好 SQL?
  6. drawer的用法_MMDrawerController抽屉侧边栏的简单使用
  7. 思考、学习新技术的原则和方式
  8. 局部临时表 全局临时表 表变量
  9. web程序设计基础——学习通选择判断复习(1)
  10. 数据库服务器sa 密码修改,修改vcenter数据库账号密码
  11. ensp 华为路由器配置远程登陆(telnet,ssh)
  12. 清华姚班出身,95后博士生从网瘾少年到发表顶会论文的科学家
  13. 计算机系统中引入虚拟内存的好处是什么,虚拟内存的作用是什么
  14. 为什么要进行网站流量分析?从6个方面告诉你答案
  15. Android移动开发-Android设备利用光线传感器监测光照强度的实现
  16. 51单片机8*8点阵,按键控制数字加拼音上下左右移动
  17. 《Flask Web开发:基于Python的Web应用开发实战》笔记(原创)
  18. Java编程之英文单词首字母大写
  19. Markdown插入图片操作
  20. 岭南师范学院计算机考试题库和答案,2017年岭南师范学院第51次考全国计算机等级考试NCR报考程序...

热门文章

  1. java环境变量的配置与详解,这些知识你必须拿下
  2. erlang下lists模块sort(排序)方法源码解析(二)
  3. asp.net mvc 用Redis实现分布式集群共享Session。
  4. C#心得与经验(二)
  5. SpringMVC配置项学习笔记
  6. C/C++查找一定范围内的素数(筛法)
  7. Oracle 11g 新特性 -- 自适应游标共享(Adaptive Cursor Sharing: ACS) 说明
  8. 完整的Android 新浪微博api调用DEMO
  9. Hive2.1.1、Hadoop2.7.3 部署
  10. 【一针见血】 JavaScript this