大家好,小编最近在梳理 React Hook 的相关内容,由于看视频、看书、自己做项目总觉得缺点什么,总觉得看过了,内容太简单,有啥好写的?写了也不一定有人看,但是想想,还是整理成文章分享出来,查漏补缺,用自己的思路和语言整理出来,方便日后的复习和查看,也希望能帮助到初学者入门。
React Hooks 无疑是目前 React 最令人兴奋着迷的特性之一,你可以使用更简单的函数方式创建更加友好的组件。以往只有类组件才有状态管理,现在通过 useState Hooks 函数式的声明方式管理数据状态,简化生命周期的相关钩子函数等。
换句话说,我们构建React组件时不需要通过类的形式进行定义,Hooks 是一项革命性的功能,它将简化您的代码,使其易于阅读、维护、隔离测试以及在您的项目中重用。你只需要花极端的时间进行熟悉它们,剩下的就是在实践中联系就行了。
为了快速上手,小编还是建议使用官方的脚手架 Create React App ,安装命令如下:
npm i -g create-react-app
全局完成安装后,你就可以开始创建 React 应用了
npx create-react-app myapp
创建完成后,在项目目录下运行命令,运行你的 React 项目
cd myapp npm start
在学习 Hooks 中的状态管理之前,我们先复习下,在类组件中怎么进行状态管理的,有了对比,才能更好的理解 Hooks 的状态管理。示例代码如下:
import React from "react"; export default class ClassDemo extends React.Component { constructor(props) { super(props); this.state = { name: "Agata" }; this.handleNameChange = this.handleNameChange.bind(this); } handleNameChange(e) { this.setState({ name: e.target.value }); } render() { return ( <section> <form autoComplete="off"> <section> <label htmlFor="name">Name</label> <input type="text" name="name" id="name" value={this.state.name} onChange={this.handleNameChange} /> </section> </form> <p>Hello {this.state.name}</p> </section> ); } }
注:如果你是通过 Create React App 创建项目,只需要将 App.js 里的内容替换成上述内容即可。
运行你的应用程序,在浏览器里,你将会看到如下效果:
接下来,给自己一点时间,去理解上述的代码,我们在构造函数里,使用this的方式声明了 name 状态,并将一个 handleNameChange 函数绑定到组件实例中。在函数中,我们通过 this.setState 的方式改变状态的值。当用户在文本输入框输入值时,就会触发 handleNameChange 函数,更改 name 状态值。
现在,我们将使用 useState Hook 编写此代码的新版本。它的语法如下所示:
const [state, setState] = useState(initialState);
useState 函数将返回一个包含两个元素的数组:
initialState 参数,则是初始化 state 状态的默认值。
基于以上基础知识后,我们来改下第三部分类组件的声明方式,示例代码如下:
import React, { useState } from "react"; export default function HookDemo(props) { const [name, setName] = useState("Agata"); function handleNameChange(e) { setName(e.target.value); } return ( <section> <form autoComplete="off"> <section> <label htmlFor="name">Name</label> <input type="text" name="name" id="name" value={name} onChange={handleNameChange} /> </section> </form> <p>Hello {name}</p> </section> ); }
接下来,我们在停下来思考下,这个代码与类组件有什么不同,是不是觉得代码更加紧凑容易理解了,代码少了不少,而且运行效果完全相同没有啥差异,具体的差异如下:
注意:使用 React Hooks 时,请确保在组件顶部声明它们,不要在条件语句中声明它们。
如果有多个 useState Hooks 该怎么办呢?答案很简单:只需调用另一个 useState Hook。您可以根据需要多次声明,前提是您不会使组件过于复杂,以下代码是声明多个 useState Hooks 的示例:
import React, { useState } from "react"; export default function HookDemo(props) { const [name, setName] = useState("Agata"); const [location, setLocation] = useState("Nairobi"); function handleNameChange(e) { setName(e.target.value); } function handleLocationChange(e) { setLocation(e.target.value); } return ( <section> <form autoComplete="off"> <section> <label htmlFor="name">Name</label> <input type="text" name="name" id="name" value={name} onChange={handleNameChange} /> </section> <section> <label htmlFor="location">Location</label> <input type="text" name="location" id="location" value={location} onChange={handleLocationChange} /> </section> </form> <p> Hello {name} from {location} </p> </section> ); }
我们可以通过函数的方式在 setCount 进行更改状态的值,可以通过参数的形式获取当前状态的值,然后在此基础上进行更改,但是直接更改状态值或通过函数的形式更改状态值,有何不同呢?
setCount(count + 1)
setCount(prev => prev + 1)
两种结果都是一致的,如果你懒得了解,建议直接使用后者,避免出错。在讲解之前,小编先出一道题,看看能否答对:
function A () { const [count, setCount] = useState(4); setCount(count + 1); setCount(count + 1); console.log('A: ', count) // ? } function B () { const [count, setCount] = useState(4); setCount(prev => prev + 1); setCount(prev => prev + 1); console.log('B: ', count) // ? }
看完这道题,你打答案是啥?(答案:A: 5;B: 6)。有没有回答对呢?在A里面第二个setCount会覆盖第一个,因为他们的初始值都是4,但使用函数版本来设置状态会记得prevState的当前状态进行更改。
还有一个需要你关注的是,如下段代码所示 ,Pass the state 是每一次状态更改都会运行,而 Pass the function 只运行一次:
function init () { console.log('run function'); return 4; } // Run Everytime const [count, setCount] = useState(4); const [count, setCount] = useState(init()); // Run only the very first time when your component render const [count, setCount] = useState(() => init());
从上图所示,如果你使用的是函数方式的初始化状态值,每次更改状态值,只打印一次。
如果是 Object 的状态值,我们只想更改个别属性的值,为了避免出错,我们该怎么做呢?如下段代码所示:
const [state, setState] = useState({count: 4, name: 'blue'}); setState(prevState => {...prevSate, count: prevSate.count + 1}; console.log(state); // {count: 5, name: 'blue'} setState(prevState => {count: prevSate.count + 1}; console.log(state); // {count: 5} name消失,因为会更改整个状态的值
基础知识学完了,接下来我们就亲自动手做个练习吧,检测下是否掌握,具体的需求如下:
1、能够添加商品的名称和价格;2、展示已添加的商品列表;3、支持商品的删除;就这些基础的需求,我们运用今天的知识动手练习下吧,界面示意图如下所示:
如上图所示,我们创建三个组件:表单组件 IngredientForm ,商品清单列表组件IngredientList,清单页面组件 Ingredients(将表单组件和品清单列表组件包含在内),还有一个UI组件card(表单组件应用其样式)。
接下来我们使用 Create React App 脚手架创建项目,删除多于的文件,最后调整后的目录结构如下图所示,保留app.js,index.js,index.css;新建组件目录 components 和相关组件文件:
接下来,我们修改原有的 index.js 文件,示例代码如下所示:
import Reactfrom'react'; import ReactDOMfrom'react-dom'; import'./index.css'; import Appfrom'./App'; ReactDOM.render(<App />, document.getElementById('root')); //index.js
修改 app.js 文件,引入我们清单页面组件 Ingredients,示例代码如下:
import Reactfrom'react'; import Ingredientsfrom'./components/Ingredients/Ingredients'; const App= props => { return <Ingredients />; }; export defaultApp; //app.js
最后修改我们全局的 index.css 的代码
@importurl('https://fonts.googleapis.com/css?family=Open+Sans:400,700&display=swap'); * { box-sizing: border-box; } html, body { font-family: 'Open Sans', sans-serif; margin: 0; } button { font: inherit; background: #ff2058; padding: 0.5rem 2rem; color: white; border: 1px solid #ff2058; margin: 0.5rem 0; border-radius: 5px; cursor: pointer; } button:hover, button:active{ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.26); } button:focus{ outline: none; } /* index.css */
由于代码比较简单,这里就不解释了,罗列代码,主要方便大家按照我的步骤一步步的完成。
我们在 components 目录下新建一个目录 Ingredients,这个目录下存放一些和清单业务相关的组件,接下来我们创建一个清单表单组件 IngredientForm,基于原型图,这个组件可以抽象出一个公共的UI组件Card, 方便我们布局表单界面。
1、在UI目录下新建 Card.js 组件,示例代码如下:
importReactfrom'react'; import'./Card.css'; constCard= props => { return<divclassName="card">{props.children}</div>; }; export defaultCard; //components/UI/Card.js
这里我们使用了 props.children 这个特性可以包含子组件,我们就可以在其中嵌套我们的表单组件了。
.card{ padding: 1rem; border-radius: 5px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26); } /* components/UI/Card.css */
2、新建 IngredientForm.js 组件文件
基于原型图,这里我们有两个表单输入框和提交按钮,分别用于录入商品名称、单价和提交数据。这里我们就可以用到 Hooks 的状态值了,初始化内容为空,这里我们定义了 enteredTitle,enteredAmount 两个状态值,同时在提交按钮上绑定了一个属性方法 submitHandler,通过子组件向父组件传值的形式,将当前用户操作更改的状态值传递给父组件 Ingredients,说了这么多,还是看看代码吧,示例代码如下:
import React, {useState}from'react'; import Card from'../UI/Card'; import'./IngredientForm.css'; const IngredientForm = React.memo(props => { const [enteredTitle, setEnteredTitle] =useState(''); const [enteredAmount, setEnteredAmount] =useState(''); const submitHandler = event => { event.preventDefault(); props.onAddIngredient({ title: enteredTitle, amount: enteredAmount }); }; return( <section className="ingredient-form"> <Card> <form onSubmit={submitHandler}> <div className="form-control"> <labelhtmlFor="title">商品名称</label> <input type="text" id="title" value={enteredTitle} onChange={event => { set EnteredTitle(event.target.value); }} /> </div> <div className="form-control"> <label htmlFor="amount">单价</label> <input type="number" id="amount" value={enteredAmount} onChange={event => { setEnteredAmount(event.target.value); }} /> </div> <div className="ingredient-form__actions"> <button type="submit">添加</button> </div> </form> </Card> </section> ); }); export default IngredientForm; //components/Ingredients/IngredientForm.js
3、新建 IngredientForm.css 文件
.ingredient-form{ width: 30rem; margin: 2rem auto; max-width: 80%; } .form-control label, .form-control input { display: block; width: 100%; } .form-control input { font: inherit; padding: 0.1rem 0.25rem; border: none; border-bottom: 2px solid #ccc; margin-bottom: 1rem; } .form-control input:focus{ outline: none; border-bottom-color: #ff2058; } .ingredient-form__actions{ display: flex; justify-content: space-between; align-items: center; } /* components/Ingredients/IngredientForm.css */
CSS代码比较简单这里就不过多介绍了。
1、列表组件 IngredientList.js
接下来新建一个列表组件 IngredientList,显示已添加的购物清单,这里包含两个属性,状态数据属性 ingredients (父组件向子组件传值)和 一个删除事件的函数 onRemoveItem(向引用的父组件传值)。示例代码如下,比较简单,在这里就不过多解释了:
import Reactfrom'react'; import'./IngredientList.css'; const IngredientList= props => { return( <section className="ingredient-list"> <h2>我的购物清单</h2> <ul> {props.ingredients.map(ig => ( <li key={ig.id} onClick={props.onRemoveItem.bind(this, ig.id)}> <span>商品名称:{ig.title}</span> <span>单价:{ig.amount}</span> <span>删除</span> </li> ))} </ul> </section> ); }; export default IngredientList; //components/Ingredients/IngredientList.js
2、新建 IngredientList.css
.ingredient-list{ width: 30rem; max-width: 80%; margin: auto; } .ingredient-list h2 { border-bottom: 3px solid #ccc; padding-bottom: 1rem; } .ingredient-list ul { list-style: none; margin: 0; padding: 0; } .ingredient-list li { margin: 1rem 0; padding: 0.5rem 1rem; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.26); display: flex; justify-content: space-between; } /* components/Ingredients/IngredientList.css */
最后我们新建父组件容器页面 Ingredients.js,将我们刚才创建的列表组件 IngredientList 和 IngredientForm 组件放置其中。
1、运用 State Hook 的数据状态的特性,新建 userIngredients 数据状态, 用于向子组件 IngredientList 传值,渲染购物清单列表。
2、接下来我们继续声明添加购物清单函数 addIngredientHandler, 将其绑定至 IngredientForm 子组件onAddIngredient 的属性,用于接收子组件的传值,通过 setUserIngredients 方法,在里面声明函数将接收的值添加至当前的数组中。
3、最后我们添加删除当前清单内容的函数 removeIngredientHandler,将其绑定至 IngredientList 的属性 onRemoveItem,接收子组件传过来的清单ID,通过在 setUserIngredients 方法里定义函数,借助数组的 filter 方法删选非当前ID的内容。
说了这么多,还是看代码比较直接,示例代码如下:
import React, {useState} from'react'; import IngredientFormfrom'./IngredientForm'; import IngredientListfrom'./IngredientList'; const Ingredients= () => { const [userIngredients, setUserIngredients] =useState([]); const addIngredientHandler = ingredient => { setUserIngredients(prevIngredients => [ ...prevIngredients, { id: Math.random().toString(), ...ingredient } ]); }; const removeIngredientHandler = ingredientId =>{ setUserIngredients(prevIngredients=> prevIngredients.filter(ingredient => ingredient.id !== ingredientId) ); }; return( <divc className="App"> <IngredientForm onAddIngredient={addIngredientHandler} /> <section> <IngredientList ingredients={userIngredients} onRemoveItem={removeIngredientHandler} /> </section> </div> ); }; export default Ingredients;
到这里,我们的项目就完成了,在当前项目目录下运行 npm start,如果一切正常你就会看到如下视频所示的效果。
到这里关于 State Hook 的部分就介绍完了,本篇文章有些长,感谢你的阅读,你可以点击阅读原文体验本文的案例部分,如果你想获取源码请回复”r1”,小编建议亲自动手做一下,这样才能加深对State Hook 的认知,下一篇本系列文章将会介绍 useEffect,敬请期待。
注:本文属于原创文章,版权属于「前端达人」公众号及 qianduandaren.com 所有,未经授权,谢绝一切形式的转载