React Hooks教程

Hooks 介绍

1. Hooks 是什么 ?

  • Hooks 是 React 16.8 新增的特性,它可以让你在不编写 class(类组件) 的情况下使用 state 以及其他的 React 特性
  • React 一直都提倡使用函数组件的,但是函数组件没有实例不能操作state,也没有生命周期函数,只有类组件才有.
  • 如果有一天你在编写函数组件中, 突然意识到需要向其添加一些 state, 怎么办呢? 以前的做法是必须将其它转化为 类组件, 现在呢? Hooks出现之后, 你可以在现有的函数组件中配合 Hooks 直接来操作state了
  • 从功能上来看 : 类组件 ≈ 函数组件 + Hooks
  • 在 React API中, 凡是 use 开头的 都是 Hooks
    • 比如 : useState、useEffect、useCallback、useContext等等

2. 为什么要学习 Hooks ?

  • 之前 存在的问题

    • 类组件 : 生命周期难以理解,更难熟练运用
    • 复用状态的render-props和高阶组件思想理解起来不容易
    • 最火的状态管理解决方案 Redux,概念多, 难以理解
  • 学习hooks后 :

    • 生命周期可以不用学。react hooks使用全新的理念来管理组件的运作过程。 (useEffect)
    • 高阶组件不用学。React hooks能够完美解决高阶组件想要解决的问题,并且更靠谱。 (自定义hook)
    • redux不再是必须品。我们能够通过其他方式管理组件状态。 (useReducer + useContext)

3. 学习建议

  • Hooks 和现有代码可以同时工作,你可以渐进式地使用他们。

  • 不用急着迁移到 Hooks。因为 Hooks 是在函数组件中使用的, 如果全部迁移就要重构类组件为函数组件了, 工程过大

useState

1. 说明

  • useState 是允许你在 React 函数组件中添加 state 的 Hook (钩子)。

2. 语法

2.1 语法格式

    const [state, setState] = useState(initialValue)

2.2 语法说明

  • 参数 : state变量的初始值
  • 返回值 : 数组, 并且通过解构数组获取仅有的两个元素
    • 第一个元素 : state 就是一个状态变量
    • 第二个元素 : setState 是一个用于修改状态的 函数

3. 使用

3.1 声明 State 变量

import { useState } from 'react'
// 声明一个叫 “count” 的 state 变量
const [count, setCount] = useState(0);

3.2 读取 State

<div> {  count  } </div>

3.3 更新 State

<button onClick={ () => setCount( count + 1 ) } > 累加 count </button>
<button onClick={ () => setNum( num + 1 ) } > 累加 num </button>

3.4 总结

 1:  import React, { useState } from 'react';
 2:
 3:  function Counter () {
 4:    const [count, setCount] = useState(0);
 5:
 6:    return (
 7:      <div>
 8:        <p>  { count } </p>
 9:        <button onClick={ () => setCount( count + 1 ) }> +1 </button>
10:      </div>
11:    );
12:  }

# 第1: 引入 React 中的 useState 。
# 第4: 在 Counter 组件内部,我们通过调用 useState 声明了一个新的 state 变量。它返回一对值给到我们命名的变量上。我们把变量命名为 count,因为它存储的是点击次数。我们通过传 0 作为 useState 唯一的参数来将其初始化为 0。第二个返回的值本身就是一个函数。它让我们可以更新 count 的值,所以我们叫它setCount。
# 第9: 当用户点击按钮后,我们传递一个新的值给 setCount。React 会重新渲染 Counter 组件,并把最新的 count 传给它。

4. Demo

  • 步骤

    1. 搭建结构 和 样式
    2. 设置行内样式 方便js操作数据
    3. 使用 Slider 修改数据
  • 结构

# Rectangle.js 
const Rectangle = () => {
  // 初始状态
  const [width, setWidth] = useState(50)
  const [height, setHeight] = useState(50)
  const [radius, setRadius] = useState(5)
  // 行内样式
  const style = {
    width: `${width}px`,
    height: `${height}px`,
    borderRadius: `${radius}px`
  }

  return (
    <div className="container">
      <p>width</p>
      <Slider min={50} max={300} onChange={val => setWidth(val)}></Slider>
      <p>height</p>
      <Slider min={50} max={300} onChange={val => setHeight(val)}></Slider>
      <p>radius</p>
      <Slider min={5} max={150} onChange={val => setRadius(val)}></Slider>
      {/* 方形元素 */}
      <div style={style} className="rectangle"></div>
    </div>
  )
}
  • 样式
.container {
  padding: 20px;
}

.container .am-slider {
  padding: 10px 0;
}

.rectangle {
  width: 100px;
  height: 100px;
  background: pink;
  margin: 0 auto;
  margin-top: 50px;
}

useEffect

1. 说明

  • Effect Hook 可以让你在函数组件中执行副作用操作
    • 副作用 : 数据获取、设置/销毁定时器以及手动更改 React 组件中的 DOM 都属于副作用
    • 在函数组件中,每当DOM完成一次渲染,都会有对应的副作用执行
  • 官网提示 :
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
  • 个人建议 :
    • 我们要抛开生命周期的固有思维, 还要注意: 生命周期和useEffect是完全不同的。
  • 思想
function组件中,每当DOM完成一次渲染,都会有对应的副作用执行,useEffect用于提供自定义的执行内容,它的第一个参数(作为函数传入)就是自定义的执行内容。为了避免反复执行,传入第二个参数(由监听值组成的数组)作为比较(浅比较)变化的依赖,比较之后值都保持不变时,副作用逻辑就不再执行。

2. 语法

2.1 语法格式

 useEffect(callback, deps)
  - callback : 回调
  - deps : 依赖数组

2.2 语法说明

  • 回调执行

    • DOM渲染完, 执行一次 useEffect 的回调
    • 每次更新之后也会执行 useEffect 的回调
    • 每次我们重新渲染,都会生成新的 effect,替换掉之前的
    • 总结 : 某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染
  • 依赖数组

    • 可以通过 deps 依赖项 来控制 回调执行的次数

3. 使用

3.1 基本使用

  • 验证 : 回调执行
useEffect(() => {
  console.log('useEffect')
})
// 更新
<div>函数组件-{count} - {num}</div>
<button onClick={() => setCount(count + 1)}>count+1</button>

- React 会在每次渲染后都运行 Effect

3.1 修改标题案例

// 累加
useEffect(() => {
   console.log('useEffect')
  document.title = `您点击了 ${count}`
})

3.2 根据依赖项执行 useEffect

  • 需求 : 只根据count 的变化执行 useEffect
// 初始值
const [count, setCount] = useState(0)
const [num, setNum] = useState(0)

// 点击
<div>函数组件-{count} - {num}</div>
<button onClick={() => setCount(count + 1)}>count+1</button>
<button onClick={() => setNum(num + 1)}>num+1</button>

// useEffect
useEffect(() => {
  console.log('useEffect')
  // 假如说 只让count 变化的时候才打印 ,num变化的不打印
}, [count])
// }, [count, num]) 依赖项 count 和 num

依赖数组就是用来控制是否应该触发 Effect,从而能够减少不必要的计算,从而优化了性能。具体而言,只要依赖数组中的每一项与上一次渲染相比都没有改变,那么就跳过本次 Effect 的执行

3.3 定时器

  • 依赖项 改为 空数组, 永远只会执行一次
  • 空数组,不会有变化内容
// 点击
<button onClick={() => setNum(num + 1)}>num+1</button>

useEffect(() => {
  setInterval(() => {
    console.log('定时器')
  }, 1000)
})
// 原因 : 每次点击 新的 useEffect, 创建一个新的定时器

useEffect(() => {
  setInterval(() => {
    console.log('定时器')
  }, 1000)
},[])   
// 需要 添加一个空数组  只执行一次

3.3 发送请求

// 点击
<button onClick={() => setNum(num + 1)}>num+1</button>

// 请求
useEffect(() => {
  axios
    .get('https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata')
    .then(res => {
    console.log('请求结果', res)
  })
}, [])

3.5 清除定时器

  • 定时器
useEffect(() => {
  setInterval(() => {
    console.log('定时器')
  }, 1000)
})
// 原因 : 每次点击 新的 useEffect, 创建一个新的定时器
  • 只创建一次
useEffect(() => {
  setInterval(() => {
    console.log('定时器')
  }, 1000)
},[])
// 依赖空数组  只创建一个定时器
  • 打印卸载
useEffect(() => {
  setInterval(() => {
    console.log('定时器')
  }, 1000)

  return () => {
    console.log('卸载')
  }
}, [])

// 演示卸载 : 
const Parent = () => {
const [isShow, setShow] = useState(true)
return (<div>
      <div onClick={() => setShow(false)}></div>
      {isShow && <Child></Child>}
</div>)}
  • 清除定时器
// let timer 如果把 timer 放到全局的 这里会有警告 后面再解释

useEffect(() => {
  const timer = setInterval(() => {
    console.log('定时器')
  }, 1000)

  return () => {
    console.log('卸载')
    clearInterval(timer)
  }
}, [])

3.5 总结

useEffect(callback, [])  ==> 类似 : componentDidMount()
useEffect(callback, [count])  ==> 类似 : componentDidUpdate()
useEffect(()=>{
  return ()=> {
    // 卸载函数
  }
}, [count])  ==> 类似 : componentWillUnmount()

useRef

1. 说明

  • useRef 返回一个可变的 ref 对象,返回的 ref 对象在组件的整个生命周期内保持不变。
  • 用途 :
    • 绑定DOM节点,或者React元素
    • 保持可变变量

2. 语法

2.1 语法格式

// 创建ref对象
const myRef = useRef()

2.2 语法说明

import { useRef } from 'react' // 引入

// 创建ref对象
const myRef = useRef()

// 获取
myRef.current

3. 使用

3.1 用途1- 保持可变变量

// 创建 ref 对象
const myRef = useRef(10)  // 参数: 初始值

// 点击按钮 赋值   
<button onClick={ () => console.log('ref值', myRef.current ++ )  }></button>

3.2 用途2- 绑定DOM节点

// 创建 ref 对象
 const iptRef = useRef()

// 绑定
<input ref={iptRef} type="text" />

// 获取值
console.log('iptRef', iptRef.current.value)

3.3 useRef 用在定时器上

  • 报警告
let timer;  # 报警告  => 使用 useRef

useEffect(() => {
  timer = setInterval(() => {
    console.log('定时器')
  }, 1000)

  return () => {
    console.log('卸载')
    clearInterval(timer)
  }
}, [])
  • 使用 useRef 用在定时器timerId 保存变量
// 创建 timerRef
const timerRef = useRef() 

useEffect(() => {
  // 存值
  timerRef.current = setInterval(() => {
    console.log('定时器')
  }, 1000)

  return () => {
    console.log('卸载')
    // 清除
    clearInterval(timerRef.current)
  }
}, [])

4. Demo - 动画案例

  • 结构和样式
//结构
const Child = () => {
  // 点击背景
  const clickHandler = () => {}

  return (
    <div className="ref_container" onClick={clickHandler}>
      <div className="ref_el"></div>
    </div>
  )
}
// 样式
.ref_container {
  height: 660px;
  width: 375px;
  background: pink;
  border-top: 1px solid pink;
}
.ref_container .ref_el {
  background: red;
  width: 50px;
  height: 50px;
  margin-top: 100px;
}
  • 通过 ref 获取元素
 const element = useRef();  // 创建 ref
 
 <div className="ref_el" ref={element} />     // 绑定ref元素
  • 创建第一个动画 :
import anime from 'animejs';

// 第一个动画
function ml_animate01() {
  if (element) {
    anime({
      targets: element.current,
      translateX: 300,
      backgroundColor: '#ff8822',
      borderRadius: ['0%', '50%'],
      complete: () => {}
    })
  }
}

// 点击背景
const clickHandler = () => {
  ml_animate01()
}
  • 创建第二个动画
// 第一个动画 : 移动到右侧
function ml_animate01() {
   complete: () => {
     // 第一个动画完了 执行第二个动画
     ml_animate02()
   }
}
// 第二个动画 : 返回原点
function ml_animate02() {
  if (element) {
    anime({
      targets: element.current,
      translateX: 0,
      backgroundColor: '#f00',
      borderRadius: ['50%', '0%'],
      complete: () => {}
    })
  }
}
  • 配合 useStat 和 useEffect 切换动画
// 是否开始动画    true-开始动画  false-结束动画
const [isAnimate1, setAnimate1] = useState(false)
const [isAnimate2, setAnimate2] = useState(false)

useEffect(() => {
  isAnimate1 && ml_animate01()
}, [isAnimate1])

useEffect(() => {
  isAnimate2 && ml_animate02()
}, [isAnimate2])

// 动画1
function ml_animate01() {
  ...
  complete: () => {
    setAnimate1(false) // 第一个动画结束
    setAnimate2(true)  // 第二个动画开始
  }
}
// 动画2
function ml_animate02() {
  ...  
  complete: () => {
    setAnimate2(false) // 第二个动画结束
  }
}

// 点击背景
const clickHandler = () => {
  setAnimate1(true) // 第一个动画开始
}

useReducer

1. 说明

  • useState 的替代方案。
  • 它接收一个( 形如 (oldState, action) => newState 的) reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

2. 语法

###2.1 语法格式

const [state, dispatch] = useReducer(reducer, initial);

2.2 语法说明

  • 参数 : 1-reducer 2-状态初始值
  • 返回值 : 当前的 state 以及与其配套的 dispatch 方法。

3. 使用

3.1 重写 useState (计算器示例)

  • 之前的 useState
import { useState } from 'react'

//2. 创建组件
const App = () => {
  const [counter, setCounter] = useState(0)

  return (
    <div>
      <div>函数组件 - {counter}</div>
      <button onClick={() => setCounter(counter + 1)}>+1</button>
      <button onClick={() => setCounter(counter - 1)}>+1</button>
    </div>
  )
}
  • 改造的 useReducer
import { useReducer } from 'react'
// reducer
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return state + 1
    case 'decrement':
      return state - 1
    default:
      return state
  }
}
//2. 创建组件
const App = () => {
  const [counter, dispatch] = useReducer(reducer, 10)

  return (
    <div>
      <div>函数组件 - {counter}</div>
      <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>+1</button>
    </div>
  )
}

3.2 useReducer 使用场景

  • 在某些场景下,useReducer 会比 useState 更适用 : 例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      return state
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useContext

1. 说明

  • useContext,能够让我们在函数组件中使用context的能力, 替换之前的 context.Consumer

2. 语法

2.1 语法格式

const value = useContext(MyContext);

2.2 语法说明

  • 参数 : 一个 context对象
  • 返回值 : 返回 Provider 提供的 value 值

2.3 使用说明

  • 回忆 :
    • 之前在类组件件中 , 使用 context实现从上到下跨多层组件传递数据
1. 通过 createContext() 创建 context对象
   const myContext =  React.createContext() 
   
2. 在context对象中,提供了一个自带的 Provider 组件, 用来提供数据。
   <myContext.Provider value={ 数据 }>
      ....
   </myContext.Provider>

3. 在context对象中,提供了一个自带的 Consumer 组件, 用来接收数据。
    <myContext.Consumer>
      {  data => data 就是传递过来的数据 }
   </myContext.Consumer>
  • 在函数组件中, 使用 useContext hook 可以实现三个功能
    • 功能1 : 实现从上到下跨多层组件传递数据
    • 功能2 : 兄弟组件数据交互 (共享数据)
    • 功能3 : 配合 useReducer 组合 redux

3. 使用

###3.1 功能1-跨多层组件传递数据

  • Context : context.js
// context.js 
import React from 'react'

export default React.createContext()
  • 父组件 : index.js
// 引入 context
import myContext from './demo1/context'

// 提供数据
<myContext.Provider value={'哈哈哈'}>
   ...
   <Child></Child>
</myContext.Provider>
  • 子组件 : child.js
// 引入 context
import myContext from './context'

//2. 创建组件
const Child = () => {
  // 获取 context 里面的数据
  const val = useContext(myContext)
  
  return <div>子组件 - { val }</div>
}

3.2 功能2-封装 Provider-共享数据

  • Context : context.js
import React, { useState } from 'react'

// 导出1 : context
export const myContext = React.createContext()                      # 导出   myContext

// 导出2 : 函数组件
// 重定义 Provider
export function MyProvider({ children }) {                          # 导出   函数组件
  // 初始值
  const [count, setCount] = useState(0)

  // 组合要传递的共享数据   
  const value = {
    count,
    increment: () => setCount(count + 1),
    decrement: () => setCount(count - 1)
  }

  return <myContext.Provider value={value}>{children}</myContext.Provider>
}
  • Counter1 组件
//1. 引入包
import React, { useContext } from 'react'
// 引入 context
import { myContext } from './context'                             # 引入 myContext

//2. 创建组件
const Counter1 = () => {
  const { counter, increment, decrement } = useContext(myContext) # 获取共享数据

  return (
    <div>
      <div>函数组件1 - {counter}</div>
      <button onClick={() => increment()}>函数组件 +1</button>
      <button onClick={() => decrement()}>函数组件 -1</button>
    </div>
  )
}

//3. 渲染DOM
export default Counter1
  • Counter2 组件
//1. 引入包
import React, { useContext } from 'react'

import { myContext } from './context'                              # 引入 myContext

//2. 创建组件
const Counter2 = () => {
  
  const { counter, increment, decrement } = useContext(myContext)   # 获取共享数据

  return (
    <div>
      <div>函数组件2 - {counter}</div>
      <button onClick={() => increment()}>函数组件 +1</button>
      <button onClick={() => decrement()}>函数组件 -1</button>
    </div>
  )
}

//3. 渲染DOM
export default Counter2
  • 父组件
//1. 引入包
import React from 'react'
import Counter1 from './demo2/Counter1'
import Counter2 from './demo2/Counter2'

// 引入  Provider
import { MyProvider } from './context'                            # 引入 MyProvider

//2. 创建组件
const App = () => {
  return (
    <div>
                                                               #   包裹着要共享数据的组件 
      <MyProvider>
        <Child1></Child1>
        <Child2></Child2>
      </MyProvider>
    
    </div>
  )
}
//3. 导出组件
export default App

3.3 功能3- useContext 和 useReducer 组合 redux

  • 文件1 : context.js
import React from 'react'
import { useReducer } from 'react'

// 导出 context
export const myContext = React.createContext()

// 第一个 reducer
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return state + 1
    case 'decrement':
      return state - 1
    default:
      return state
  }
}


// 导出 Provider
export const MyProvider = ({ children }) => {
  const [count, dispatch] = useReducer(reducer, 0)

  const values = {
    count,
    dispatch
  }

  return <myContext.Provider value={values}>{children}</myContext.Provider>
}

  • 文件2 : 父组件 index.js
//1. 引入包
import React from 'react'
import Counter1 from './Counter1'
import Counter2 from './Counter2'
import { MyProvider } from './context'

//2. 创建组件
const App = () => {
  return (
    <div>
      <div>函数组件</div>
        <MyProvider>
          <Counter1></Counter1>
          <Counter2></Counter2>
        </MyProvider>
    </div>
  )
}

//3. 导出组件
export default App
  • 文件3 : 子组件 countert1.js
//1. 引入包
import React from 'react'
import { useContext } from 'react'
import { myContext } from './context'

//2. 创建组件
const Counter1 = () => {
  const { count, dispatch } = useContext(myContext)

  return (
    <div>
      <div>Counter1 - {count}</div>
      <button onClick={() => dispatch({ type: 'increment' })}>+1</button>
    </div>
  )
}

//3. 导出组件
export default Counter1
  • 文件4 :子组件 counter2.js
//1. 引入包
import React from 'react'
import { useContext } from 'react'
import { myContext } from './context'

//2. 创建组件
const Counter2 = () => {
  const { count, dispatch } = useContext(myContext)

  return (
    <div>
      <div>Counter2 - {count}</div>
      <button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
 
    </div>
  )
}

//3. 导出组件
export default Counter2

###3.4 组合 redux 重写黑马课程todos案例 (简易版)

useMemo

1. 说明

  • useMemo 可以让你在函数组件中 缓存计算结果

2. 语法

###2.1 语法格式

const memoizedValue = useMemo(() => fn(a,b), deps[a,b])

2.2 语法说明

  • 它接收两个参数,
    • 第一个参数为回调函数 (计算过程fn,必须返回一个结果),
    • 第二个参数是依赖项(数组),当依赖项中某一个发生变化,结果将会重新计算。
  • 注意 : 因为是缓存计算结果,所以参数1回调函数里面一定要有return返回值

2. 使用

  • 使用 useMemo 之前
    • 问题 : 每次点击干扰项 other, 都要重新计算一下,所以需要缓存值
const App = () => {
  const [count, setCount] = useState(0)
  const [other, setOther] = useState(0)

  // 累加的方法
  function calcCount(count) {
    console.log('重新计算了一次')
    let _sum = 0
    for (var i = 1; i <= count; i++) {
      _sum += i
    }
    return _sum
  }
  // 结算的结果
  let sum = calcCount(count)

  return (
    <div>
      <div>
        参数 : {count} 累加 : {sum}
      </div>
      <button onClick={() => setCount(count + 1)}>修改参数</button>
      <button onClick={() => setOther(other + 1)}>干扰项 {other}</button>
    </div>
  )
}
  • 使用 useMemo
  // 累加的方法 
  function calcCount(count) {
    console.log('重新计算了一次')
    let _sum = 0
    for (var i = 1; i <= count; i++) {
      _sum += i
    }
    return _sum               #  记忆的是 _sum
  }
  // 结算的结果 
  let sum = useMemo( () => calcCount(count),  [count])      #   使用 useMemo 

  return (
    <div>
      <div>
        参数 : {count} 累加 : {sum}
      </div>
      <button onClick={() => setCount(count + 1)}>修改参数</button>
      <button onClick={() => setOther(other + 1)}>干扰项 {other}</button>
    </div>
  )

useCallback

1. 说明

  • useCallback 可以让你在函数组件中 缓存计算函数

2. 语法

###2.1 语法格式

const fnA = useCallback(fnB, [a])

2.2 语法说明

  • useCallback 会将我们传递给它的函数fnB返回,并且将这个结果fnA 缓存;
  • 当依赖a变更时,会返回新的函数。

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

3. 使用

// const inputChange = e => {
//   console.log(e.target.value)
// }

// 这里面的箭头函数 依赖 count
// 如果 count 有变化, 就会创建一个新的 ()=>{}
// 如果 count 没有变化,
const inputChange = useCallback(() => {
	console.log(count)
}, [count])

return (
    <div>
        <div>函数组件 - {count}</div>
        <input type="text" onChange={inputChange} />
        <button onClick={() => setCount(count + 1)}>改count</button>
        <button onClick={() => setOther(other + 1)}>改干扰项</button>
    </div>
)

4. 验证

  • 返回的是函数,我们无法很好的判断返回的函数是否变更,所以我们可以借助ES6新增的数据类型Set来判断,
// 创建一个  全局的  set
const set = new Set();

// 缓存函数   
const inputChange = useCallback(e => {
    console.log('input获取的值', e.target.value)
}, [])

// 如果 地址一样 是不会添加的 地址不一样才会添加
set.add(inputChange)
console.log('长度', set.size)

5. 使用场景

//2. 创建组件
const App = () => {
  const [count, setCount] = useState(0)
  // 干扰
  const [other, setOther] = useState(0)

  const inputChange = useCallback(() => {
    console.log(count)
  }, [count])

  set.add(inputChange)
  console.log('set个数', set.size)

  return (
    <div>
      <div>
        函数组件 - {count} - {other}
      </div>
      <input type="text" onChange={inputChange} />
      <button onClick={() => setCount(count + 1)}>1</button>
      <button onClick={() => setOther(other + 1)}>1</button>
      <Child f={inputChange}></Child>
    </div>
  )
}

// memo 是缓存外界传来的函数 不变会不更新
// 自己如果不变的话 自己就不更新了
const Child = memo(({ f }) => {
  console.log('child')

  return <div>Child组件</div>
})

自定义 Hook

1. 自定义Hook1 - useMouse

  • 封装 useMouse
//1. 引入包
import { useState, useEffect } from 'react'

//2. 创建组件
const useMouse = () => {
  const [mouse, setMouse] = useState({ x: 0, y: 0 })

  const handleMouse = e => {
    setMouse({
      x: e.clientX,
      y: e.clientY
    })
  }

  useEffect(() => {
    window.addEventListener('mousemove', handleMouse)

    return () => {
      window.removeEventListener('mousemove', handleMouse)
    }
  }, [])

  return mouse
}

export default useMouse
  • 使用 useMouse
// 1. 引入
import useMouse from './useMouse'

const App = () => {
  // 2. 获取 坐标值
  const mouse = useMouse()

  return (
    <div>
      // 3. 使用坐标
      <div> 函数组件 {mouse.x} - {mouse.y} </div>
    </div>
  )
}

2. 自定义 Hook2 - useRequest

  • 封装的自定义hook
import { useEffect, useState } from 'react'
import axios from 'axios'

const useRequest = () => {
  const [isLoading, setLoading] = useState(true)
  const [response, setResponse] = useState([])

  // 钩子
  useEffect(() => {
    if (!isLoading) return

    setTimeout(() => {
      getData()
    }, 3000)
  }, [isLoading])

  // 获取数据
  function getData() {
    axios
      .get(
        'https://api-hmugo-web.itheima.net/api/public/v1/goods/qsearch?query=1'
      )
      .then(res => {
        console.log('useeffect', res.data.message)
        setResponse(res.data.message)
      })
      .finally(() => {
        setLoading(false)
      })
  }

  return { isLoading, setLoading, response }
}

export default useRequest

  • 使用hook
import useRequest from './useRequest'

//2. 创建组件
const App = () => {
  // isLoading
  //  true : 正在加载
  //  false  : 加载完成
  const { isLoading, setLoading, response } = useRequest()

  return (
    <div>
      <button onClick={() => setLoading(true)}>点击再次刷新</button>
      {isLoading && <div>正在努力加载中</div>}
      {response.map(v => {
        return <div key={v.goods_id}>{v.goods_name}</div>
      })}
    </div>
  )
}

案例

1. 准备工作

  • antd-mobile ( Badge徽标数) https://mobile.ant.design/components/badge-cn/
  • 接口1 https://api-hmugo-web.itheima.net/api/public/v1/goods/qsearch?query=1
  • 接口2 https://api-hmugo-web.itheima.net/api/public/v1/home/catitems
  • 自定义hook

2. 使用步骤

2.1 界面搭建

  • 创建 App => Home - Hot - Setting
  • tab的结构 和 样式
  • 点击 tab 切换 结构和高亮样式
  • Badge 加上徽标和样式
// 结构
const App = () => {
  const [tabIndex, setTabIndex] = useState(0)

  return (
    <div>
      {/* tab 部分 */}
      <div className="tab">
        <div
          className={tabIndex === 0 ? 'active' : ''}
          onClick={() => setTabIndex(0)}
        >
          <Badge text="10" style={{ marginLeft: 10 }}>
            首页
          </Badge>
        </div>
        <div
          className={tabIndex === 1 ? 'active' : ''}
          onClick={() => setTabIndex(1)}
        >
          <Badge text="5" style={{ marginLeft: 10 }}>
            热门
          </Badge>
        </div>
        <div
          className={tabIndex === 2 ? 'active' : ''}
          onClick={() => setTabIndex(2)}
        >
          设置
        </div>
      </div>
      {/* 列表部分 */}
      <div className="content">
        {tabIndex === 0 && <Home></Home>}
        {tabIndex === 1 && <Hot></Hot>}
        {tabIndex === 2 && <Setting></Setting>}
      </div>
    </div>
  )
}

// 样式
.tab {
  height: 40px;
  background: yellow;
  display: flex;
}
.tab > div {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  border-bottom: 2px solid pink;
}
.tab > div.active {
  border-bottom: 2px solid red;
}

.am-activity-indicator {
  justify-content: center;
}

* {
  touch-action: none;
}

.am-list-item .am-list-line .am-list-extra {
  overflow: visible;
}

2.2 发送请求-Home-Hot

  • 封装自定义hook 请求 - request.js
import axios from 'axios'
import { useEffect, useState, useCallback } from 'react'

// 首页请求
const useRequestHome = url => {
  const [isLoading, setLoading] = useState(true)
  const [response, setResponse] = useState([])

  const getData = useCallback(() => {
    axios
      .get(url)
      .then(res => {
        setResponse(res.data.message)
      })
      .finally(() => {
        setLoading(false)
      })
  }, [url])

  useEffect(() => {
    if (!isLoading) return
    setTimeout(() => {
      getData()
    }, 3000)
  }, [getData, isLoading])

  return { isLoading, response }
}

export default useRequestHome
  • home.js 使用 自定义 hook
//1. 引入核心包
import React from 'react'
import  useRequestHome  from './request'
import { ActivityIndicator } from 'antd-mobile'

//2. 类组件
const Home = () => {
  let url =
    'https://api-hmugo-web.itheima.net/api/public/v1/goods/qsearch?query=1'
  const { isLoading, response } = useRequestHome(url)

  if (isLoading) {
    return <ActivityIndicator></ActivityIndicator>
  }

  return (
    <div>
      {response.map(v => {
        return <div key={v.goods_id}>{v.goods_name}</div>
      })}
    </div>
  )
}

//3. 导出组件
export default Home
  • hot.js 使用 自定义 hook
//1. 引入核心包
import React from 'react'
import  useRequestHome  from './request'
import { ActivityIndicator } from 'antd-mobile'

//2. 类组件
const Hot = () => {
  let url = 'https://api-hmugo-web.itheima.net/api/public/v1/home/catitems'
  const { isLoading, response } = useRequestHome(url)

  if (isLoading) {
    return <ActivityIndicator></ActivityIndicator>
  }

  return (
    <div>
      {response.map(v => {
        return <div key={v.goods_id}>{v.name}</div>
      })}
    </div>
  )
}

//3. 导出组件
export default Hot

2.3 封装 Provider

import React from 'react'
import { useState } from 'react'

// 导出两个 context
export const myContext = React.createContext()

// 导出 Provider
export const Provider = ({ children }) => {
  const [unReadHome, setUnReadHome] = useState(5)
  const [unReadHot, setUnReadHot] = useState(10)

  const values = {
    unReadHome,
    setUnReadHome,
    unReadHot,
    setUnReadHot
  }

  return <myContext.Provider value={values}>{children}</myContext.Provider>
}
  • 提供数据 - index.js (父组件)
export default () => (
  <Provider>
    <App></App>
  </Provider>
)

2.4 读取 徽标

import { Provider, myContext } from './context'

const { unReadHome, unReadHot } = useContext(myContext)

<Badge text={unReadHome} style={{ marginLeft: 10 }}>
  首页
</Badge>

<Badge text={unReadHot} style={{ marginLeft: 10 }}>
热门
  </Badge>

2.5 设置页面 - Setting

//1. 引入核心包
import React, { useContext, useState } from 'react'
import { myContext } from '../context'
import { List, Stepper } from 'antd-mobile'

//2. 类组件
export default function Setting() {
  const {unReadHome, setUnReadHome, unReadHot, setUnReadHot} = useContext(myContext)

  return (
    <div>
      <List>
        <List.Item
          wrap
          extra={
            <Stepper
              showNumber
              min={0}
              value={unReadHome}
              onChange={setUnReadHome}
            />
          }
        >
          首页未读
        </List.Item>
        <List.Item
          extra={
            <Stepper
              showNumber
              min={0}
              value={unReadHot}
              onChange={setUnReadHot}
            />
          }
        >
          热门未读
        </List.Item>
      </List>
    </div>
  )
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值