React中Hooks的学习一些总结

37 篇文章 1 订阅
  1. hooks是什么?

Hooks是钩子,可以让函数组件’钩入’React.state及生命周期等特性的函数,是React16.8新增特性.可以在不编写class的情况下使用state及其他React特性.

*只能在函数组件中使用

理解:通过Hooks为函数组件钩入class组件的特性

  1. React组件的本质

React是用于构建用户界面的JS库;

React组件:从Model到View的映射, Model对应的就是React中的State和Props

公式:组件(State+Props)=UI

  1. 函数式组件好处

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 (
    <div>
      count是:{count}
      <h1>第一个hooks函数useState</h1>
    <button onClick={()=>{setCount(count+1)}}>count+1</button>
   </div>
  );
}

export default App;

(2)useState的两种格式:

a. 传入值: useState(0) useState(‘abc’)

b. 传入回调: setState((上一次的值)=>{return 新值})

小结

  1. 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 (
        <div>
            <h1>count:{count}</h1>
            <button onClick={handleClick}>连续+1三次(不能实现)</button>
            <button onClick={handleClick2}>连续+1三次回调</button>
        </div>
    );
}
  1. setXXX的参数是函数式,可以用来对state进行叠加

setCount(100)
setCount((initState) => {
  return newState
})
  1. 倒计时小案例

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 (
        <div>
            <h1>count:{count}</h1>
            <button onClick={handleClick}>每秒倒计时-1</button>
        </div>
    )
}
export default App
  1. useState的更新过程

const App=()=> {
    console.log('Count')//点击就执行
    const [count, setCount] = useState(() => {
        console.log('useState');//只会执行一次
        return 0
    })
    return (
        <div>
            <h1>count:{count}</h1>
            <button onClick={()=>{setCount(count+1)}}>点击+1</button>
        </div>
    )
}

小结:

状态更新,整个组件的逻辑会再走一次.

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副作用

  1. 理解副作用

函数式组件:(但凡和渲染页面无关的都可以理解为副作用,但也不是所有和渲染页面无关的代码都要写到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 (
      <div>
           class组件: {this.state.num}
            <button onClick={() => {
                this.setState((state)=>({ num: state.num + 1 }))
                this.setState((state)=>({ num: state.num + 1 }))
                this.setState((state)=>({ num: state.num + 1 }))
            }}>num + 1</button>
      </div>
    )
  }
}

函数组件:

//函数式组件实现点击+3
import React, { useState } from 'react'
 const App = () => {
     const [num, setNum] = useState(0)
     return (
         <div>
             {num}
             <button onClick={() => {
                 setNum((num)=>num+1)
                 setNum((num)=>num+1)
                 setNum((num)=>num+1)
             }
             }
             >+1</button> 
         </div>
     )
}
export default App
  1. 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 (
         <div>
             {num}
             <button onClick={() => { setNum((num)=>num+1) }} >+1</button> 
         </div>
     )
}
export default App
  1. useEffect设置依赖项

useEffect有两个参数:

参数1:副作用函数.

参数2:执行副作用函数的依赖项,它决定了什么时机执行参数1(副作用函数).

  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,...])

这里的依赖项就是组件中定义的状态.

  1. 典型应用场景-定时器

const App = () => {
    //以下代码是一个非常典型的闭包
    const [count, setCount] = useState(100)
    useEffect(() => {
        setInterval(() => {
            console.log(Date.now());
            //setCount(count-1) 这么写永远是99,不会继续减下去了
        setCount((count)=>count-1)
       }, 1000);
    }, []);
    return (
        <div>
          倒计时100个数  {count}     
        </div>
    )
}

小结:

1.useEffect没有依赖项,副作用函数如何执行?

创建的时候更新一次,以后每次更新的时候都会执行一次.

2.useEffect依赖项是[],副作用函数如何执行?

只执行一次,创建的时候执行.

3.useEffect依赖项是[count],副作用函数如何执行?

依赖项变化的时候执行,第一次会执行.(下方是链接useEffect)

这里附上一个useEffect的链接地址,可以看详细的讲解:

useEffect完全指南

最后通过一个小的购物车案例来使用一下这两个钩子,加深一下理解.

三.购物车案例

(1)新建目录

MyHeader GoodItem MyFooter

安装bootstrap@4.5.0

在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 (
        <div className='my-header'>标题
        </div>
    )
}
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 (
        <div className='my-goods-item'>
            <div className='left'>
                <div className='custom-control custom-checkbox'>
                    <input type='checkbox' className='custom-control-input' id='input'></input>
                    <label className='custom-control-label' htmlFor='input'>
                        <img src='https://www.escook.cn/vuebase/pics/1.png' alt=''>

                        </img>
                    </label>
                </div>
            </div>
            <div className='right'>
                <div className='top'>商品名称</div>
                <div className='bottom'>
                    <span className='price'>$商品价格</span>
                    <span >conuter组件</span>
                   </div>
                
            </div>
        </div>
    )
}
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 (
        <div className='my-footer'>
            <div className='custom-control custom-checkbox'>
                <input type='checkbox' className='custom-control-input' id='footerCheck'></input>
                <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>
    )
}
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 (
        <div className='app'>
            <MyHeader />
            {/* 传递给子组件 */}
            {
                list.map((goods)=> <GoodItem key={goods.id} goods={goods} />)
            }
            <MyFooter/>
        </div>
    )
}

子组件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 (
        <div className='my-goods-item'>
            <div className='left'>
                <div className='custom-control custom-checkbox'>
                    <input type='checkbox' className='custom-control-input' id='input'></input>
                    <label className='custom-control-label' htmlFor='input'>
                        <img src={goods.goods_img} alt=''>

                        </img>
                    </label>
                </div>
            </div>
            <div className='right'>
                <div className='top'>{goods.goods_name}</div>
                <div className='bottom'>
                    <span className='price'>{goods.goods_price}</span>
                    <span >conuter组件{goods.goods_count}</span>
                   </div>
                
            </div>
        </div>
    )
}
//加校验规则/此处要添加
GoodItem.propsTypes = {
    goods:PropsTypes.object.isRequired
}

export default GoodItem

(3)商品选中功能

  1. 在父组件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) => <GoodItem key={goods.id}
      setGoodsSelectedState={setGoodsSelectedState} 
      goods={goods} />)
   }
  1. 子组件内部调用

 const { goods ,setGoodsSelectedState} = props//需要验证一下goods的类型
  <input type='checkbox' className='custom-control-input'
                        checked={goods.goods_state}
                        onChange={() => {setGoodsSelectedState(goods.id,!goods.goods_state) }}
                        onClick={() => {
                        }}
                         id={goods.id}></input>//注意此处的id,要不然选不中
      <label className='custom-control-label' htmlFor={goods.id}>//注意此处的htmlFor要不然选不中
                        <img src={goods.goods_img} alt=''> </img> 
                    </label>
    //加校验规则
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 list={list} setSelectAll={setSelectAll} />

MyFooter/index.js中

 const { setSelectAll, list } = props
 const isSelecetdAll=list.every(item=>item.goods_state===true)//判断是否全选
  <input type='checkbox' className='custom-control-input'
                    onChange={() => { setSelectAll(!isSelecetdAll) }}
                    checked={isSelecetdAll}
                    id='footerCheck'>
     </input>
     //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)
  <span className='price'>${total}</span>
   <button type='button' className='footer-btn btn btn-primary'>结算({totalCount})</button>

(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 / 事件/ 定时器.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值