携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
React Hooks
- React Hooks 介绍
- useState hook
- useEffect hook
hooks是什么?
目标
能够说出react hooks是什么?
Hooks 是什么
Hooks:钩子、钓钩、钩住。 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
作用: 为函数组件提供状态、生命周期等原本 在Class 组件中才提供的功能
- Hooks 只能在函数组件中使用
- 可以理解为通过 Hooks 为函数组件钩入 class 组件的特性
Hooks 前后,组件开发模式的对比
-
React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
-
React v16.8 及其以后:
- class 组件(提供状态) + 函数组件(展示内容)
- Hooks(提供状态) + 函数组件(展示内容)
- 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件
小结
- 有了 Hooks 以后,不能再把函数组件称为 __ __ __了,因为 Hooks 可以为函数组件提供了状态。
- Hooks 是一些可以让你在 __ _组件里“钩入” __ _ 及 __ __ 等特性的 ****
为什么要有 Hooks
目标
能够说出为什么要有hooks,hooks能解决什么问题?
内容
两个角度:1 组件的状态逻辑复用 2 class 组件自身的问题
react组件的本质
React 是用于构建用户界面的 JavaScript 库 。
React组件是对特定功能的封装,主要用来对UI进行拆分。
React 组件的模型其实很直观,就是从 Model 到 View 的映射,这里的 Model 对应到 React 中就是 state 和 props
公式:组件(State+Props) = UI
class 组件自身的问题
在根据状态来渲染UI这件事上,class 组件并没有发挥它最重要的功能:
- 组件之间很少继承
- 组件之间很少相互访问
函数式组件的好处
- 函数本身比较简单,更好的胜任根据状态来渲染UI这件事
- hooks让函数组件内部有了维护状态的能力
- hooks带来了组件的逻辑复用能力
小结
- 组件的本质工作是状态到UI的映射
- 相比类组件,hooks+函数组件能更好的胜任这个工作
- hooks带来了更强大的 __ __ 的能力
hooks的使用策略
目标
能够理解在react中什么场景应该使用hooks
策略
-
react没有计划从React中移除class
-
Hook 和现有代码可以同时工作,你可以渐进式地使用:
- 不推荐直接使用 Hooks 大规模重构现有组件
- 推荐新功能用 Hooks,复杂功能实现不了的,也可以继续用 class
- 找一个功能简单、非核心功能的组件开始使用 hooks
-
class 组件相关的 API 在hooks中可以不用
- state与setState
- 钩子函数,
componentDidMount、componentDidUpdate、componentWillUnmount `this相关的用法
-
原来学习的内容还是要用的
- JSX:
{}、onClick={handleClick}、条件渲染、列表渲染、样式处理等 - 组件:函数组件、组件通讯
- React 开发理念:
单向数据流、状态提升等
- JSX:
总结
- react官方 __ _React中移除class
- 可以在项目中同时使用 __ 和Class式组件
useState-基本使用-认识第一个hooks
学习的第一个hooks
目标
能够使用useState为函数组件提供状态
使用场景
当你想要在函数组件中,使用组件状态时,就要使用 useState 这个Hook 了,
作用
为函数组件提供状态(state)
步骤
-
导入。
useState函数 -
调用
useState函数,传入初始值,返回状态和修改状态的函数 -
使用
- 在 JSX 中展示
状态 - 特定的时机调用修改状态的函数来改状态
- 在 JSX 中展示
示例
import { useState } from 'react'
// useState 是hook,hook是use开头的函数
const Count = () => {
// 0 是初始值
// 返回值是一个数组
const stateArray = useState(0)
// 状态值 -> 0
const state = stateArray[0]
// 修改状态的函数
const setState = stateArray[1]
return (
<div>
{/* 展示状态值 */}
<h1>useState Hook -> {state}</h1>
{/* 点击按钮,让状态值 +1 */}
<button onClick={() => setState(state + 1)}>+1</button>
</div>
)
}
-
参数:状态初始值。比如,传入 0 表示该状态的初始值为 0
- 注意:此处的状态可以是任意值(比如,数值、字符串等),而 class 组件中的 state 必须是对象
-
返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
使用数组解构简化代码
使用数组解构简化 useState 的使用。约定:修改状态的函数名称以 set 开头,后面跟上状态的名称
// 解构出来的名称可以是任意名称
const [state, setState] = useState(0)
const [age, setAge] = useState(0)
const [count, setCount] = useState(0)
小结
-
hooks是以是开头的 __
-
useState的作用是 __ __ __ ___
-
useState的格式是:
- 入参是?
- 返回值是?
useState-处理表单元素
目标
能够获取表单元素的值
思路
- 用useState初始化内容和修改内容的方法
- 向input元素上设置value和onChange属性
代码
import React, { useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
const [content, setContent] = useState('')
return (
<div>
{content}
<input value={content} onChange={(e) => setContent(e.target.value)} />
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
小结
useState可以轻松完成受控组件的功能
useState-setXXX回调函数格式
目标
掌握useState的参数是函数的格式
useState 两种格式
格式1:传入值
useState(0) useState('abc')
格式2:传入回调
useState(() => { return 初始值 })
- 回调函数的返回值就是状态的当前值
- 回调函数只会触发一次
使用场景
格式1:传入值
如果状态就是一个普通的数据(比如,字符串、数字、数组等)都可以直接使用 useState(普通的数据)
格式2:传入回调
- 初始状态需要经过一些计算得到。
useState(()=>{这里有一些计算, return 结果}))
setXXX的参数可以是回调
状态需要迭代累计。 setXXXXX((上一次的值) => { return 新值 })
示例-经典场景
希望state有连续叠加的效果
import React, { useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
const [count, setCount] = useState(0)
const hClick1 = () => {
setCount(count+1)
setCount(count+1)
setCount(count+1)
}
const hClick2 = () => {
}
return (
<div>
count:{count}
<button onClick={hClick1}>多次连续setCount-值</button>
<button onClick={hClick2}>多次连续setCount-值</button>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
小结
-
useState的参数有两种格式
- 值
const [count, setCount] = useState(0) - 回调函数
const [count, setCount] = useState(()=>{ return 初始值 })
- 值
-
setXXX的参数有两个格式,其中函数式可以用来对state进行叠加
setCount(100) setCount(initState => { // ... return newState })
## 练习
点击按钮,开始从60倒计时
```js
import React, { useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
const [count, setCount] = useState(10)
const hClick = () => {
setInterval(() => {
setCount((count) => count - 1)
}, 1000)
}
return (
<div>
<h1>{count}</h1>
<button onClick={hClick}>倒数10个数</button>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
useState-组件的更新过程
目标
了解使用useState之后,组件的更新过程
背景
函数组件没有生命周期
更新过程
示例代码
import { useState } from 'react'
const Count = () => {
console.log('Count...')
const [count, setCount] = useState(0)
return (
<div>
{/* 展示状态值 */}
<h1>useState Hook -> {count}</h1>
{/* 点击按钮,让状态值 +1 */}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
更新过程
函数组件使用 useState hook 后的执行过程,以及状态值的变化:
-
组件第一次渲染:
- 执行该函数中的代码逻辑
- 调用
useState(0)将传入的参数作为状态初始值,即:0 - 渲染组件,此时,获取到的状态 count 值为: 0
用户点击按钮,调用 setCount(count + 1) 修改状态,因为状态发生改变,所以,该组件会重新渲染
-
组件第二次渲染:
-
再次执行该组件中的代码逻辑
-
再次调用
useState(0):- 此时 React 内部会拿到最新的状态值而非初始值
- 该案例中最新的状态值为 1
-
再次渲染组件,此时,获取到的状态 count 值为:1
-
useState 的初始值(参数)只会在组件第一次渲染时生效
const [count, setCount] = useState(()=>{
console.log('useState...') // 这句只会输出一次
return 0
})
也就是说,以后的每次渲染,useState 获取到都是最新的状态值。React 组件会记住每次最新的状态值!
小结
状态更新,整个组件的逻辑重新运行一次;
useState只会在组件第一次渲染时使用状态的初值,随后都会使用状态的最新值
useState 这个 Hook 就是用来管理 state 的,它可以让函数组件具有维持状态的能力。也就是说,在一个函数组件的多次渲染之间,这个 state 是共享的。
useState-最佳实践
目标
了解useState的最佳实践方式
如何为函数组件提供多个状态?
两种方案
- 方案1:
useState({状态1, 状态2.....}) - 方案2:
useState(状态1) useState(状态2)
推荐使用方案2
调用 useState Hook 多次即可,每调用一次 useState Hook 可以提供一个状态。
注意:
hooks: setXXX(新值) ==> 用新值去替换之前的值
class: setState({要修改的属性})
useState的使用规则
规则
-
useState只能直接出现在 函数组件 内部
-
useState不能嵌套在 if/for/
原因: React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook。 可以通过开发者工具进行查看
useEffect-理解副作用
目标
能够说出什么是副作用
理解副作用
副作用(Side Effects;Adverse Reactions)系指应用治疗量的药物后所出现的治疗目的以外的药理作用
事物的主要作用之外的,就是副作用。
感冒药:
- 主作用:用于感冒引起的头痛,发热,鼻塞,流涕,咽痛等
- 副作用:可见困倦、嗜睡、口渴、虚弱感
函数式组件:
- 主作用:就是根据数据(state/props)渲染 UI
- 副作用:数据(Ajax)请求、手动修改 DOM、开启定时器,清空定时器,添加事件监听,删除事件, localStorage 操作等
总结
对于react组件来说,除了渲染UI之外的其他操作,都可以称之为副作用。
useEffect-基本使用
目标
能够在函数组件中使用useEffect,掌握它的使用格式和执行时机
使用步骤
// 1. 导入useEffect
import { useEffect } from 'react'
// 2. 使用useEffect
useEffect(() => {
console.log('useEffect 1 执行了,可以做副作用')
})
useEffect(() => {
console.log('useEffect 2 执行了,可以做副作用')
})
执行时机
render工作完成之后,执行Effect;
如果定义了多个,则顺序执行;
案例
import React, { useState, useEffect } from 'react'
import ReactDom from 'react-dom'
export default function App () {
useEffect(() => {
console.log('useEffect')
document.title = 'count' + count
})
const [count, setCount] = useState(0)
return (
<div
onClick={() => {
setCount(count + 1)
}}>
函数组件{count}
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
小结
- 在实际开发中,副作用是不可避免的。因此,react 专门提供了 __ __ 来处理函数组件中的副作用
- 所有的副作用操作只能写在useEffect中吗?
- 步骤?
- 执行时机?
useEffect-设置依赖项
目标
能够设置useEffect的依赖,只在 count 变化时,才执行相应的 effect。
问题导入
import React, { useState, useEffect } from 'react'
import ReactDom from 'react-dom'
export default function App () {
useEffect(() => {
console.log('useEffect,更新title')
document.title = 'count' + count
})
const [count, setCount] = useState(0)
const [n, setN] = useState(1)
return (
<div>
<h1>useEffect的依赖项</h1>
<button
onClick={() => {
setCount(count + 1)
}}>
点击了{count}次,会更新到文档的标题上
</button>
<div>
n:{n}
<button onClick={() => setN(n + 1)}>点击,让n+1</button>
</div>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
问题:
- 点击第2行中的代码,副作用会执行吗?
- 有必要执行么?
useEffect的依赖项
useEffect有两个参数:
参数1: 副作用函数。
参数2:执行副作用函数的依赖项:它决定了什么时机执行参数1(副作用函数)
useEffect的完整格式
情况1:不带第二个参数。执行时机:每次更新之后都要执行
情况2:带第二个参数,参数是空数组。执行时机:只执行第一次
useEffect(() => {
// 副作用函数的内容
}, [])
使用场景:1 事件绑定 2 发送请求获取数据 等。
情况3:带第二个参数(数组格式),并指定了依赖项。执行时机:(1)初始执行一次 (2)依赖项的值变化了,执行一次
useEffect(() => {
// 副作用函数的内容
}, [依赖项1,依赖项2,....])
这里的依赖项就是组件中定义的状态。
小结
- useEffect没有依赖项,副作用函数将如何执行?
- useEffect的依赖项是[], 副作用函数将如何执行?
- useEffect的依赖项是[count], 副作用函数将如何执行?
参考:
- useEffect完全指南:overreacted.io/zh-hans/a-c…
- 拓展阅读:dan abramov面试字节
useEffect典型应用场景-定时器
问题导入
import React, { useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
const [count, setCount] = useState(0)
setInterval(() => {
setCount((count) => count + 1)
})
return (
<div>
{count}
{/* <button onClick={hClick}>点击</button> */}
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
购物车案例
目标
使用函数式组件+hooks来完成
组件划分
基本步骤
- 初始化项目基本结构
- 封装 MyHeader 组件
- 封装 MyFooter 组件
- 商品列表数据展示
- 封装 GoodsItem 组件
- 封装 MyCounter 组件
项目初始化
- 清理目录
- 安装bootstrap
npm i bootstrap@4.5.0 - 引入bootstrap样式文件
// App.js
import 'bootstrap/dist/css/bootstrap.css'
import './app.scss'
return <div className="app"></div>
新增样式 App.scss
.app {
padding-top: 45px;
padding-bottom: 50px;
}
封装MyHeader 组件
目标
封装并使用Myheader组件
思路
- 创建组件,引入样式,导出组件
- 使用组件
新建组件
src/components/MyHeader/index.js
import './index.scss'
import React from 'react'
export default function index() {
return (
<div className='my-header'>
标题
</div>
)
}
- 新建样式文件
src/components/MyHeader/index.scss
.my-header {
z-index: 999;
height: 45px;
line-height: 45px;
text-align: center;
background-color: #1d7bff;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
}
- 注意:脚手架内置了scss的支持,但是需要安装scss依赖包
npm i sass
使用组件
App.js
import React from 'react'
import MyHeader from './components/MyHeader'
export default function App() {
return (
<div>
<MyHeader>购物车</MyHeader>
</div>
)
}
封装MyFooter组件
目标
封装购物车的Footer组件
步骤
- 创建Footer组件
- 提供Footer样式
- 在App.js中渲染
核心代码:
- 创建Footer组件
src/components/MyFooter.js
import React from 'react'
import './index.scss'
export default function MyFooter() {
return (
<div className="my-footer">
<div className="custom-control custom-checkbox">
<input type="checkbox" className="custom-control-input" id="footerCheck" />
<label className="custom-control-label" htmlFor="footerCheck">全选</label>
</div>
<div>
<span>合计:</span>
<span className="price">¥ 100</span>
</div>
<button type="button" className="footer-btn btn btn-primary">结算 (0)</button>
</div>
)
}
- 提供Footer样式
src/components/MyFooter.scss
.my-footer {
z-index: 999;
position: fixed;
bottom: 0;
width: 100%;
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
background: #fff;
.price {
color: red;
font-weight: bold;
font-size: 15px;
}
.footer-btn {
min-width: 80px;
height: 30px;
line-height: 30px;
border-radius: 25px;
padding: 0;
}
}
- 在
App.js中渲染
import React from 'react'
import MyHeader from './components/MyHeader'
import MyFooter from './components/MyFooter'
export default function App() {
return (
<div>
<MyHeader>购物车</MyHeader>
<MyFooter></MyFooter>
</div>
)
}
封装GoodsItem组件
目标
封装GoodsItems组件
步骤
创建组件src/components/GoodsItem/index.js
import React from 'react'
import './index.scss'
export default function GoodsItem() {
return (
<div className="my-goods-item">
<div className="left">
<div className="custom-control custom-checkbox">
<input type="checkbox" className="custom-control-input" id="input" />
<label className="custom-control-label" htmlFor="input">
<img
src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
alt=""
/>
</label>
</div>
</div>
<div className="right">
<div className="top">商品名称</div>
<div className="bottom">
<span className="price">¥ 商品价格</span>
<span>counter组件</span>
</div>
</div>
</div>
)
}
-
准备样式
建立
src/components/GoodsItem/index.scss,内容如下:
.my-goods-item {
display: flex;
padding: 10px;
border-bottom: 1px solid #ccc;
.left {
img {
width: 120px;
height: 120px;
margin-right: 8px;
border-radius: 10px;
}
.custom-control-label::before,
.custom-control-label::after {
top: 50px;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.bottom {
display: flex;
justify-content: space-between;
padding: 5px 0;
.price {
color: red;
font-weight: bold;
}
}
}
}
- 使用组件
import React from 'react'
import './App.scss'
import MyHeader from './components/MyHeader'
import MyFooter from './components/MyFooter'
import GoodsItem from './components/GoodsItem'
export default function App() {
return (
<div className="app">
<MyHeader>购物车</MyHeader>
<GoodsItem></GoodsItem>
<GoodsItem></GoodsItem>
<GoodsItem></GoodsItem>
<GoodsItem></GoodsItem>
<MyFooter></MyFooter>
</div>
)
}
GoodsItems-商品列表渲染
目
完成商品列表的数据渲染
步骤:
- app组件根据数据渲染商品列表
- GoodsItem接收数据进行渲染
核心代码
初始数据
const arr = [
{
id: 1,
goods_name:
'班俏BANQIAO超火ins潮卫衣女士2020秋季新款韩版宽松慵懒风薄款外套带帽上衣',
goods_img: 'https://www.escook.cn/vuebase/pics/1.png',
goods_price: 108,
goods_count: 1,
goods_state: true,
},
{
id: 2,
goods_name:
'嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮',
goods_img: 'https://www.escook.cn/vuebase/pics/2.png',
goods_price: 129,
goods_count: 1,
goods_state: true,
},
{
id: 3,
goods_name:
'思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套',
goods_img: 'https://www.escook.cn/vuebase/pics/3.png',
goods_price: 198,
goods_count: 1,
goods_state: false,
},
{
id: 4,
goods_name:
'思蜜怡卫衣女加绒加厚2020秋冬装新款韩版宽松上衣连帽中长款外套',
goods_img: 'https://www.escook.cn/vuebase/pics/4.png',
goods_price: 99,
goods_count: 1,
goods_state: false,
},
{
id: 5,
goods_name:
'幂凝早秋季卫衣女春秋装韩版宽松中长款假两件上衣薄款ins盐系外套潮',
goods_img: 'https://www.escook.cn/vuebase/pics/5.png',
goods_price: 156,
goods_count: 1,
goods_state: true,
},
{
id: 6,
goods_name: 'ME&CITY女装冬季新款针织抽绳休闲连帽卫衣女',
goods_img: 'https://www.escook.cn/vuebase/pics/6.png',
goods_price: 142.8,
goods_count: 1,
goods_state: true,
},
{
id: 7,
goods_name:
'幂凝假两件女士卫衣秋冬女装2020年新款韩版宽松春秋季薄款ins潮外套',
goods_img: 'https://www.escook.cn/vuebase/pics/7.png',
goods_price: 219,
goods_count: 2,
goods_state: true,
},
{
id: 8,
goods_name:
'依魅人2020休闲运动衣套装女秋季新款秋季韩版宽松卫衣 时尚两件套',
goods_img: 'https://www.escook.cn/vuebase/pics/8.png',
goods_price: 178,
goods_count: 1,
goods_state: true,
},
{
id: 9,
goods_name:
'芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬',
goods_img: 'https://www.escook.cn/vuebase/pics/9.png',
goods_price: 128,
goods_count: 1,
goods_state: false,
},
{
id: 10,
goods_name:
'Semir森马卫衣女冬装2019新款可爱甜美大撞色小清新连帽薄绒女士套头衫',
goods_img: 'https://www.escook.cn/vuebase/pics/10.png',
goods_price: 153,
goods_count: 1,
goods_state: false,
},
]
用useState维护数据
const [list, setList] = useState(arr)
app.js渲染组件
{list.map((item) => (
<GoodsItem key={item.id} {...item}></GoodsItem>
))}
子组件goodsItem渲染数据
import React from 'react'
import './index.scss'
export default function GoodsItem({
goods_count,
goods_img,
goods_name,
goods_price,
goods_state,
id,
}) {
return (
<div className="my-goods-item">
<div className="left">
<div className="custom-control custom-checkbox">
<input
type="checkbox"
className="custom-control-input"
checked={goods_state}
id={id}
/>
<label className="custom-control-label" htmlFor={id}>
<img src={goods_img} alt="" />
</label>
</div>
</div>
<div className="right">
<div className="top">{goods_name}</div>
<div className="bottom">
<span className="price">¥ {goods_price}</span>
<span>counter组件</span>
</div>
</div>
</div>
)
}
GoodsItem-商品选中功能
目标
完成商品的选中切换功能
步骤
- 注册onChange事件
- 子传父修改状态
核心代码
子组件 goodsItem
调用父组件传入的函数
<input
type="checkbox"
className="custom-control-input"
checked={goods_state}
id={id}
onChange={() => changeState(id)}
/>
父组件app.js
定义函数并传给子组件
const changeState = (id) => {
setList(
list.map((item) => {
if (item.id === id) {
return {
...item,
goods_state: !item.goods_state,
}
} else {
return item
}
})
)
}
{list.map((item) => (
<GoodsItem
key={item.id}
{...item}
changeState={changeState}
></GoodsItem>
))}
MyFooter-商品全选功能
目标
完成商品全选切换功能
核心代码
-
子组件
定义isCheckAll。通过分析list中是否全部选中来决定
给checkbox上绑定onChange
<input
type="checkbox"
className="custom-control-input"
id="footerCheck"
checked={isCheckAll}
onChange={() => checkAll(!isCheckAll)}
/>
- 父组件
定义函数checkAll
并传给子组件使用
const checkAll = (value) => {
setList(
list.map((item) => {
return {
...item,
goods_state: value,
}
})
)
}
<MyFooter checkAll={checkAll} />
MyFooter-商品数量与价格的显示
步骤:
- 父组件把list传递给子组件
<MyFooter list={list}></MyFooter>
- 子组件计算总数量和总价钱
const totalCount = list
.filter((item) => item.goods_state)
.reduce((prev, item) => prev + item.goods_count, 0)
const totalPrice = list
.filter((item) => item.goods_state)
.reduce((prev, item) => prev + item.goods_price * item.goods_count, 0)
useEffect数据持久化
用useEffect做数据持久化
// 获取
const [list, setList] = useState(() => {
return JSON.parse(localStorage.getItem('list')) || arr
})
useEffect(() => {
localStorage.setItem('list', JSON.stringify(list))
}, [list])
``