hooks是什么?
Hooks是钩子,可以让函数组件’钩入’React.state及生命周期等特性的函数,是React16.8新增特性.可以在不编写class的情况下使用state及其他React特性.
*只能在函数组件中使用
理解:通过Hooks为函数组件钩入class组件的特性
React组件的本质
React是用于构建用户界面的JS库;
React组件:从Model到View的映射, Model对应的就是React中的State和Props
公式:组件(State+Props)=UI
函数式组件好处
hooks带来了组件的逻辑复用能力.
一.第一个hooks ->useState
(1)使用方法:
a. 导入: useState函数
b. 调用useState函数,传入初始值,返回状态和修改状态的函数
c. 使用(演示过程)
import React,{useState} from 'react'
function App() {
//useState,100是初始值
//res是数组,有两个元素,res[0]是状态,res[1]是修改状态的函数
//res[1]是新状态值,-->UI就会更新
// const res = useState(100)
// console.log('res1',res)
// const count = res[0]
// const setCount = res[1]
// console.log('res2', setCount)
//推荐的解构赋值写法
const [count,setCount]=useState(100)
return (
count是:{count}
第一个hooks函数useState
);
}
export default App;
(2)useState的两种格式:
a. 传入值: useState(0) useState(‘abc’)
b. 传入回调: setState((上一次的值)=>{return 新值})
useState的两种格式:
a.值:const [count,setCount]=useState(0)
b.回调函数:`const [count,setCount]=useState(()=>{return 初始值})`
import React, { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
//注意:这里不加const就成未定义了,会报错,所以要申明(类组件不需要)
const handleClick = () => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
const handleClick2 = () => {
setCount((count) => {
return count + 1
})
setCount((count) => {
return count + 1
})
setCount((count) => {
return count + 1
})
}
return (
count:{count}
);
}
setXXX的参数是函数式,可以用来对state进行叠加
setCount(100)
setCount((initState) => {
return newState
})
倒计时小案例
import React, { useState } from 'react'
function App() {
const [count,setCount]=useState(3)
const handleClick = () => {
const timer= setInterval(() => {
setCount((count) => {
if (count===1) {
clearInterval(timer)
}
return count-1
} )
}, 1000);
}
return (
count:{count}
)
}
export default App
useState的更新过程
const App=()=> {
console.log('Count')//点击就执行
const [count, setCount] = useState(() => {
console.log('useState');//只会执行一次
return 0
})
return (
count:{count}
)
}
状态更新,整个组件的逻辑会再走一次.
useState只会在组件第一次渲染时使用状态的初值,随后都会使用状态的新值.
useState这个Hooks就是用来管理state的,在一个函数的多次渲染之间,这个state的共享的.
5.useState最佳实践
(1)如何为函数组件提供多个状态?
方案1:useState({状态1,状态2,...})
方案2:useState(状态1) useState(状态2) (推荐),调用useState Hooks多次即可,每调用一次useState Hook可以提供一个状态.
注意: hooks: setXXX(新值) ==>用新值去替换之前的值
class: setState({要修改的属性})
二.第二个钩子useEffect副作用
理解副作用
函数式组件:(但凡和渲染页面无关的都可以理解为副作用,但也不是所有和渲染页面无关的代码都要写到useEffect中)
主作用:根据数据(state/props)渲染UI
副作用:数据(Ajax)请求,手动修改DOM,localStorage操作等.
2.class组件和函数组件对比(点击+3案例)
class组件代码:
import React, { Component,useState } from 'react'
//useState 和class组件比较(点一次+3的案例)
export default class App extends Component {
state={num:0}
render() {
return (
class组件: {this.state.num}
)
}
}
函数组件:
//函数式组件实现点击+3
import React, { useState } from 'react'
const App = () => {
const [num, setNum] = useState(0)
return (
{num}
)
}
export default App
useEffect的基本使用
(1)执行时机
组件挂载时,执行一次(componentDidMount)
组件更新时,可能执行多次.(componentDidUpdate)
(2)小案例,点击改变页面标题
import React, { useState,useEffect } from 'react'
const App = () => {
const [num, setNum] = useState(0)
//副作用基本使用,点击改变页面标题
useEffect(() => {
console.log('useEffect1')
document.title='点击了按钮'+num
})
useEffect(() => {
console.log('useEffect2')
})
return (
{num}
)
}
export default App
useEffect设置依赖项
useEffect有两个参数:
参数1:副作用函数.
参数2:执行副作用函数的依赖项,它决定了什么时机执行参数1(副作用函数).
useEffect的完整格式
情况1:不带第二个参数,执行时机:只执行第一次.(基本不用这种)
情况2:带第二个参数,参数是空数组,执行时机:只执行第一次.
使用场景:1.时间绑定;2.发送请求获取数据等.
useEffect(() => {
console.log('修改标题 useEffect1')//此处只打印一次,执行一次
document.title = '点击了按钮' + num
}, [])
情况3:带第二个参数(数组格式),并指定了依赖项,执行时机:(1)初始执行一次;(2)依赖项的值变化了,执行一次.
useEffect(() => {
console.log('修改标题 useEffect1')//此处只打印一次,执行一次
document.title = '点击了按钮' + num
}, [依赖项1,依赖项2,...])
这里的依赖项就是组件中定义的状态.
典型应用场景-定时器
const App = () => {
//以下代码是一个非常典型的闭包
const [count, setCount] = useState(100)
useEffect(() => {
setInterval(() => {
console.log(Date.now());
//setCount(count-1) 这么写永远是99,不会继续减下去了
setCount((count)=>count-1)
}, 1000);
}, []);
return (
倒计时100个数 {count}
)
}
1.useEffect没有依赖项,副作用函数如何执行?
创建的时候更新一次,以后每次更新的时候都会执行一次.
2.useEffect依赖项是[],副作用函数如何执行?
只执行一次,创建的时候执行.
3.useEffect依赖项是[count],副作用函数如何执行?
依赖项变化的时候执行,第一次会执行.(下方是链接useEffect)
这里附上一个useEffect的链接地址,可以看详细的讲解:
useEffect完全指南
最后通过一个小的购物车案例来使用一下这两个钩子,加深一下理解.
三.购物车案例
(1)新建目录
MyHeader GoodItem MyFooter
在App.js中引入bootstrap样式文件
import 'bootstrap/dist/css/bootstrap.css'
.app{
padding-top: 45px;
padding-bottom: 50px;
}
.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%;
}
注意:脚手架中内置了sass的支持,但要安装sass的依赖.
yarn add sass
MyHeader 组件中的代码:
import React from 'react'
import './index.scss'
const MyHeader=() => {
return (
标题
)
}
export default MyHeader
样式结构:
.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%;
}
GoodItem 中结构代码:
import React from 'react'
import './index.scss'
const GoodItem = () => {
return (
商品名称
$商品价格
conuter组件
)
}
export default GoodItem
样式代码:
.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;
.top {
font-size: 14px;
font-weight: 700;
}
.bottom {
display: flex;
justify-content: space-between;
padding: 5px 0;
align-items: center;
.price {
color: red;
font-weight: bold;
}
}
}
}
MyFooter结构代码:
import React from 'react'
import './index.scss'
const MyFooter = () => {
return (
合计:
$100
)
}
export default MyFooter
样式代码:
.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-color: #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;
}
}
(2)铺设数据
import React, { useState}from 'react'
import MyHeader from './购物车案例组件/components/MyHeader'
import GoodItem from './购物车案例组件/components/GoodItem'
import MyFooter from './购物车案例组件/components/MyFooter'
//引入bootstrap样式
import 'bootstrap/dist/css/bootstrap.css'
import './app.scss'
//铺设商品数据
const initList = [{
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_img: "https://www.escook.cn/vuebase/pics/2.png",
goods_name: "嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮",
goods_price: 129,
goods_count: 1,
goods_state: true,
},
{
id: 3,
goods_img: "https://www.escook.cn/vuebase/pics/3.png",
goods_name: "思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套",
goods_price: 198,
goods_count: 1,
goods_state: true,
},
]
const App = () => {
const [list, setList] = useState(initList)
console.log(list, setList);
return (
{/* 传递给子组件 */}
{
list.map((goods)=> )
}
)
}
子组件GoodItem接收(接收的数据注意解构以下,拿需要的数据,另外接收的数据需要加校验规则,再把数据一铺设就行).
import React from 'react'
import PropsTypes from 'prop-types'//此处要添加
import './index.scss'
const GoodItem = (props) => {
//我们只要其中的goods,所以解构以下
// console.log(props);->接收父组件传来的数据
const { goods } = props//需要验证一下goods的类型
console.log(goods);
return (
{goods.goods_name}
{goods.goods_price}
conuter组件{goods.goods_count}
)
}
//加校验规则/此处要添加
GoodItem.propsTypes = {
goods:PropsTypes.object.isRequired
}
export default GoodItem
(3)商品选中功能
在父组件App.js中定义设置选中的方法:
//找到id为几的商品,设置它的状态是newState
const setGoodsSelectedState = (id, newState) => {
console.log('传递给子组件', id, newState);
const newList = list.map((item) => {
if (item.id===id) {
return {...item,goods_state:newState}
} else {
return {...item}
}
})
setList(newList)
}
2.传给子组件
//传递给子组件
{
list.map((goods) => )
}
子组件内部调用
const { goods ,setGoodsSelectedState} = props//需要验证一下goods的类型
{setGoodsSelectedState(goods.id,!goods.goods_state) }}
onClick={() => {
}}
id={goods.id}>//注意此处的id,要不然选不中
//加校验规则
GoodItem.propsTypes = {
goods: PropsTypes.object.isRequired,
setGoodsSelectedState:PropsTypes.func.isRequired
}
(4)商品全选功能
App.js中:
//全选的方法,传递给子组件
const setSelectAll = (newState) => {
const newList = list.map((item) => {
return {...item,goods_state:newState}
})
setList(newList)
}
MyFooter/index.js中
const { setSelectAll, list } = props
const isSelecetdAll=list.every(item=>item.goods_state===true)//判断是否全选
{ setSelectAll(!isSelecetdAll) }}
checked={isSelecetdAll}
id='footerCheck'>
//list的校验省略了...
(5)商品的数量和价格显示
MyFooter/index.js中
//商品价格总金额
const total = list.filter(item => item.goods_state).reduce((prev, current) => {
return prev + current.goods_count * current.goods_price
}, 0)
//商品数量
const totalCount = list.filter(item => item.goods_state).reduce((prev, current) => {
return prev + current.goods_count
}, 0)
${total}
(6)useEffect数据持久化
App.js中
import React, { useState, useEffect } from 'react'
const [list, setList] = useState(() => {
//如果本地有数据,从本地拿
return JSON.parse(localStorage.getItem('list')) || initList
})
//数据存储
useEffect(() => {
return localStorage.setItem('list', JSON.stringify(list))
})
useEffect:副作用,对于函数组件来说,渲染UI是主作用,与之无关的都是副作用.
常见的副作用:ajax /dom / 事件/ 定时器.