React(coderwhy)- 08(Hooks)

13 篇文章 5 订阅

认识和体验Hooks

为什么需要Hook?

Hook 是 React 16.8 的新增特性,它可以让我们在 不编写class的情况下 使用 state以及其他的React特性 (比如生命周期)。
我们先来思考一下class组件相对于函数式组件有什么优势?比较常见的是下面的优势:
class组件可以 定义自己的state ,用来 保存组件自己内部的状态
         函数式组件不可以,因为函数每次调用都会产生新的临时变量;
class组件有 自己的生命周期 ,我们可以在 对应的生命周期中完成自己的逻辑
         比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次;
         函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;
class组件可以 在状态改变时只会重新执行render函数 以及 我们希望重新调用的生命周期函数componentDidUpdate 等;
         函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次;
所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件。

Class组件存在的问题

复杂组件变得难以理解:
         我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是 随着业务的增多 ,我们的 class组件会变得越来越复杂
         比如componentDidMount中,可能就会包含大量的逻辑代码:包括 网络请求、一些事件的监听 (还需要在
componentWillUnmount中移除);
         而对于这样的class实际上非常难以拆分:因为 它们的逻辑往往混在一起 强行拆分反而会造成过度设计 增加代码的复杂度
难以理解的class:
         很多人 发现学习ES6的class是学习React的一个障碍
         比如在class中,我们 必须搞清楚this的指向到底是谁 ,所以需要花很多的精力去学习this;
         虽然我认为前端开发人员必须掌握this,但是依然处理起来非常麻烦;
组件复用状态很难
         在前面为了一些状态的复用我们需要 通过高阶组件
         像我们之前学习的 redux中connect或者react-router中的withRouter ,这些高阶组件设计的目的就是 为了状态的复用
         或者 类似于Provider、Consumer来共享一些状态 ,但是 多次使用Consumer时,我们的代码就会存在很多嵌套
         这些代码让我们 不管是编写和设计上来说,都变得非常困难

Hook的出现

Hook的出现,可以解决上面提到的这些问题;
简单总结一下hooks:
         它可以让我们在不编写class的情况下使用state以及其他的React特性
         但是我们 可以由此延伸出非常多的用法 来让我们前面所提到的问题得到解决
Hook的使用场景:
         Hook的出现 基本可以代替我们之前所有使用class组件的地方
         但是如果是一个旧的项目,你 并不需要直接将所有的代码重构为Hooks ,因为 它完全向下兼容,你可以渐进式的来使用它
         Hook 只能在函数组件中使用,不能在类组件 或者函数组件之外的地方使用
在我们继续之前,请记住 Hook 是:
         完全可选的 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
         100% 向后兼容的 Hook 不包含任何破坏性改动。
         现在可用 Hook 已发布于 v16.8.0。

import React, { PureComponent } from 'react'

class HelloWord extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      message: "Hello World"
    }
  }

  changeText() {
    this.setState({ message: "你好啊, 李银河!" })
  }

  render() {
    const { message } = this.state

    return (
      <div>
        <h2>内容: {message}</h2>
        <button onClick={e => this.changeText()}>修改文本</button>
      </div>
    )
  }
}

function HelloWorld2(props) {
  let message = "Hello World"

  // 函数式组件存在的最大的缺陷:
  // 1.组件不会被重新渲染: 修改message之后, 组件知道自己要重新进行渲染吗? 
  // 2.如果页面重新渲染: 函数会被重新执行, 第二次重新执行时, 会重新给message赋值为hello world
  // 3.类似于生命周期的回调函数: 也是没有的

  return (
    <div>
      <h2>内容2: {message}</h2>
      <button onClick={e => message = "你好啊, 李银河!"}>修改文本</button>
    </div>
  )
}

export class App extends PureComponent {
  render() {
    return (
      <div>
        <HelloWord/>
        <hr />
        <HelloWorld2/>
      </div>
    )
  }
}

export default App

计数器案例对比 

State/Effect

useState解析


认识useState 


认识Effect Hook 

需要清除Effect

 

import React, { memo, useEffect } from 'react'
import { useState } from 'react'

const App = memo(() => {
  const [count, setCount] = useState(0)

  // 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
  useEffect(() => {
    // 1.监听事件
    // const unubscribe = store.subscribe(() => {
    // })
    // function foo() {
    // }
    // eventBus.on("why", foo)
    console.log("监听redux中数据变化, 监听eventBus中的why事件")

    // 返回值: 回调函数 => 组件被重新渲染或者组件卸载的时候执行
    return () => {
      console.log("取消监听redux中数据变化, 取消监听eventBus中的why事件")
    }
  })

  return (
    <div>
      <button onClick={e => setCount(count+1)}>+1({count})</button>
    </div>
  )
})

export default App

使用多个Effect

使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:
         比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中;
使用Effect Hook,我们可以将它们分离到不同的useEffect中:
         代码不再给出
Hook 允许我们按照代码的用途分离它们, 而不是像生命周期函数那样:
         React 将按照 effect 声明的顺序依次调用组件中的 每一个 effect;

 Effect性能优化

 

import React, { memo, useEffect } from 'react'
import { useState } from 'react'

const App = memo(() => {
  const [count, setCount] = useState(0)

  // 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
  useEffect(() => {
    // 1.修改document的title(1行)
    console.log("修改title")
  })

  // 一个函数式组件中, 可以存在多个useEffect
  useEffect(() => {
    // 2.对redux中数据变化监听(10行)
    console.log("监听redux中的数据")
    return () => {
      // 取消redux中数据的监听
    }
  })

  useEffect(() => {
    // 3.监听eventBus中的why事件(15行)
    console.log("监听eventBus的why事件")
    return () => {
      // 取消eventBus中的why事件监听
    }
  })

  return (
    <div>
      <button onClick={e => setCount(count+1)}>+1({count})</button>
    </div>
  )
})

export default App

执行时机 - 控制回调执行:

import React, { memo, useEffect } from 'react'
import { useState } from 'react'

const App = memo(() => {
  const [count, setCount] = useState(0)
  const [message, setMessage] = useState("Hello World")

  useEffect(() => {
    console.log("修改title:", count)
  }, [count])

  useEffect(() => {
    console.log("监听redux中的数据")
    return () => {}
  }, [])

  useEffect(() => {
    console.log("监听eventBus的why事件")
    return () => {}
  }, [])

  useEffect(() => {
    console.log("发送网络请求, 从服务器获取数据")

    return () => {
      console.log("会在组件被卸载时, 才会执行一次")
    }
  }, [])

  return (
    <div>
      <button onClick={e => setCount(count+1)}>+1({count})</button>
      <button onClick={e => setMessage("你好啊")}>修改message({message})</button>
    </div>
  )
})

export default App

Context/Reducer

useContext的使用

import { createContext } from "react";

const UserContext = createContext()
const ThemeContext = createContext()


export {
  UserContext,
  ThemeContext
}
在组件中使用:
import React, { memo, useContext } from 'react'
import { UserContext, ThemeContext } from "./context"

const App = memo(() => {
  // 使用Context
  const user = useContext(UserContext)
  const theme = useContext(ThemeContext)

  return (
    <div>
      <h2>User: {user.name}-{user.level}</h2>
      <h2 style={{color: theme.color, fontSize: theme.size}}>Theme</h2>
    </div>
  )
})

export default App
 

useReducer 

useState的替代方案

 

import React, { memo, useReducer } from 'react'
// import { useState } from 'react'

function reducer(state, action) {
  switch(action.type) {
    case "increment":
      return { ...state, counter: state.counter + 1 }
    case "decrement":
      return { ...state, counter: state.counter - 1 }
    case "add_number":
      return { ...state, counter: state.counter + action.num }
    case "sub_number":
      return { ...state, counter: state.counter - action.num }
    default:
      return state
  }
}

// useReducer+Context => redux

const App = memo(() => {
  // const [count, setCount] = useState(0)
  const [state, dispatch] = useReducer(reducer, { counter: 0, friends: [], user: {} })

  // const [counter, setCounter] = useState()
  // const [friends, setFriends] = useState()
  // const [user, setUser] = useState()

  return (
    <div>
      {/* <h2>当前计数: {count}</h2>
      <button onClick={e => setCount(count+1)}>+1</button>
      <button onClick={e => setCount(count-1)}>-1</button>
      <button onClick={e => setCount(count+5)}>+5</button>
      <button onClick={e => setCount(count-5)}>-5</button>
      <button onClick={e => setCount(count+100)}>+100</button> */}

      <h2>当前计数: {state.counter}</h2>
      <button onClick={e => dispatch({type: "increment"})}>+1</button>
      <button onClick={e => dispatch({type: "decrement"})}>-1</button>
      <button onClick={e => dispatch({type: "add_number", num: 5})}>+5</button>
      <button onClick={e => dispatch({type: "sub_number", num: 5})}>-5</button>
      <button onClick={e => dispatch({type: "add_number", num: 100})}>+100</button>
    </div>
  )
})

export default App

Callback/Memo

useCallback

import React, { memo, useState, useCallback, useRef } from 'react'

// useCallback性能优化的点:
// 1.当需要将一个函数传递给子组件时, 最好使用useCallback进行优化, 将优化之后的函数, 传递给子组件

// props中的属性发生改变时, 组件本身就会被重新渲染
const HYHome = memo(function(props) {
  const { increment } = props
  console.log("HYHome被渲染")
  return (
    <div>
      <button onClick={increment}>increment+1</button>

      {/* 100个子组件 */}
    </div>
  )
})

const App = memo(function() {
  const [count, setCount] = useState(0)
  const [message, setMessage] = useState("hello")

  // 闭包陷阱: useCallback
  // const increment = useCallback(function foo() {
  //   console.log("increment")
  //   setCount(count+1)
  // }, [count])

  // 进一步的优化: 当count发生改变时, 也使用同一个函数(了解)
  // 做法一: 将count依赖移除掉, 缺点: 闭包陷阱
  // 做法二: useRef, 在组件多次渲染时, 返回的是同一个值
  const countRef = useRef()
  countRef.current = count
  const increment = useCallback(function foo() {
    console.log("increment")
    setCount(countRef.current + 1)
  }, [])

  // 普通的函数
  // const increment = () => {
  //   setCount(count+1)
  // }

  return (
    <div>
      <h2>计数: {count}</h2>
      <button onClick={increment}>+1</button>

      <HYHome increment={increment}/>

      <h2>message:{message}</h2>
      <button onClick={e => setMessage(Math.random())}>修改message</button>
    </div>
  )
})


// function foo(name) {
//   function bar() {
//     console.log(name)
//   }
//   return bar
// }

// const bar1 = foo("why")
// bar1() // why
// bar1() // why

// const bar2 = foo("kobe")
// bar2() // kobe

// bar1() // why

export default App

useMemo 

 

import React, { memo, useCallback } from 'react'
import { useMemo, useState } from 'react'


const HelloWorld = memo(function(props) {
  console.log("HelloWorld被渲染~")
  return <h2>Hello World</h2>
})


function calcNumTotal(num) {
  // console.log("calcNumTotal的计算过程被调用~")
  let total = 0
  for (let i = 1; i <= num; i++) {
    total += i
  }
  return total
}

const App = memo(() => {
  const [count, setCount] = useState(0)

  // const result = calcNumTotal(50)

  // 1.不依赖任何的值, 进行计算
  const result = useMemo(() => {
    return calcNumTotal(50)
  }, [])

  // 2.依赖count
  // const result = useMemo(() => {
  //   return calcNumTotal(count*2)
  // }, [count])

  // 3.useMemo和useCallback的对比
  function fn() {}
  // const increment = useCallback(fn, [])
  // const increment2 = useMemo(() => fn, [])


  // 4.使用useMemo对子组件渲染进行优化
  // const info = { name: "why", age: 18 }
  const info = useMemo(() => ({name: "why", age: 18}), [])

  return (
    <div>
      <h2>计算结果: {result}</h2>
      <h2>计数器: {count}</h2>
      <button onClick={e => setCount(count+1)}>+1</button>

      <HelloWorld result={result} info={info} />
    </div>
  )
})

export default App

Ref / LayoutEffect

useRef

绑定DOM:

import React, { memo, useRef } from 'react'

const App = memo(() => {
  const titleRef = useRef()
  const inputRef = useRef()
  
  function showTitleDom() {
    console.log(titleRef.current)
    inputRef.current.focus()
  }

  return (
    <div>
      <h2 ref={titleRef}>Hello World</h2>
      <input type="text" ref={inputRef} />
      <button onClick={showTitleDom}>查看title的dom</button>
    </div>
  )
})

export default App

useRef绑定值-解决闭包陷阱:

import React, { memo, useRef } from 'react'
import { useCallback } from 'react'
import { useState } from 'react'

let obj = null

const App = memo(() => {
  const [count, setCount] = useState(0)
  const nameRef = useRef()
  console.log(obj === nameRef)
  obj = nameRef

  // 通过useRef解决闭包陷阱
  const countRef = useRef()
  countRef.current = count

  const increment = useCallback(() => {
    setCount(countRef.current + 1)
  }, [])

  return (
    <div>
      <h2>Hello World: {count}</h2>
      <button onClick={e => setCount(count+1)}>+1</button>
      <button onClick={increment}>+1</button>
    </div>
  )
})

export default App
useImperativeHandle  

import React, { memo, useRef, forwardRef, useImperativeHandle } from 'react'

const HelloWorld = memo(forwardRef((props, ref) => {

  const inputRef = useRef()

  // 子组件对父组件传入的ref进行处理
  useImperativeHandle(ref, () => {
    return {
      focus() {
        console.log("focus")
        inputRef.current.focus()
      },
      setValue(value) {
        inputRef.current.value = value
      }
    }
  })

  return <input type="text" ref={inputRef}/>
}))


const App = memo(() => {
  const titleRef = useRef()
  const inputRef = useRef()

  function handleDOM() {
    // console.log(inputRef.current)
    inputRef.current.focus()
    // inputRef.current.value = ""
    inputRef.current.setValue("哈哈哈")
  }

  return (
    <div>
      <h2 ref={titleRef}>哈哈哈</h2>
      <HelloWorld ref={inputRef}/>
      <button onClick={handleDOM}>DOM操作</button>
    </div>
  )
})

export default App

useLayoutEffect 

 

自定义Hooks使用

自定义Hook

打印生命周期:

import React, { memo, useEffect, useState } from 'react'

function useLogLife(cName) {
  useEffect(() => {
    console.log(cName + "组件被创建")
    return () => {
      console.log(cName + "组件被销毁")
    }
  }, [cName])
}

const Home = memo(() => {
  useLogLife("home")

  return <h1>Home Page</h1>
})

const About = memo(() => {
  useLogLife("about")

  return <h1>About Page</h1>
})

const App = memo(() => {
  const [isShow, setIsShow] = useState(true)

  useLogLife("app")

  return (
    <div>
      <h1>App Root Component</h1>
      <button onClick={e => setIsShow(!isShow)}>切换</button>
      { isShow && <Home/> }
      { isShow && <About/> }
    </div>
  )
})

export default App

自定义Hook练习 

 创建Context:

import { createContext } from "react";

const UserContext = createContext()
const TokenContext = createContext()

export {
  UserContext,
  TokenContext
}

在入口文件中设置:

import { UserContext, TokenContext } from "./12_自定义Hooks/context"

import App from "./12_自定义Hooks/02_Context获取数据"

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <UserContext.Provider value={{name: "why", level: 99}}>
    <TokenContext.Provider value={'coderwhy'}>
      <App />
    </TokenContext.Provider>
  </UserContext.Provider>
);

Context抽离:

import { useContext } from "react"
import { UserContext, TokenContext } from "../context"

function useUserToken() {
  const user = useContext(UserContext)
  const token = useContext(TokenContext)

  return [user, token]
}

export default useUserToken

在组件中使用:

import React, { memo } from 'react'
import { useUserToken } from "./hooks"

// User/Token

const Home = memo(() => {
  const [user, token] = useUserToken()

  return <h1>Home Page: {user.name}-{token}</h1>
})

const About = memo(() => {
  const [user, token] = useUserToken()

  return <h1>About Page: {user.name}-{token}</h1>
})

const App = memo(() => {
  return (
    <div>
      <h1>App Root Component</h1>
      <Home/>
      <About/>
    </div>
  )
})

export default App

 抽离:

import { useState, useEffect } from "react"

function useScrollPosition() {
  const [ scrollX, setScrollX ] = useState(0)
  const [ scrollY, setScrollY ] = useState(0)

  useEffect(() => {
    function handleScroll() {
      // console.log(window.scrollX, window.scrollY)
      setScrollX(window.scrollX)
      setScrollY(window.scrollY)
    }

    window.addEventListener("scroll", handleScroll)
    return () => {
      window.removeEventListener("scroll", handleScroll)
    }
  }, [])

  return [scrollX, scrollY]
}

export default useScrollPosition

在组件中使用:

import React, { memo } from 'react'
import useScrollPosition from './hooks/useScrollPosition'
import "./style.css"

const Home = memo(() => {
  const [scrollX, scrollY] = useScrollPosition()

  return <h1>Home Page: {scrollX}-{scrollY}</h1>
})

const About = memo(() => {
  const [scrollX, scrollY] = useScrollPosition()

  return <h1>About Page: {scrollX}-{scrollY}</h1>
})

const App = memo(() => {
  return (
    <div className='app'>
      <h1>App Root Component</h1>
      <Home/>
      <About/>
    </div>
  )
})

export default App

 抽离:

import { useEffect } from "react"
import { useState } from "react"

function useLocalStorage(key) {
  // 1.从localStorage中获取数据, 并且数据数据创建组件的state
  const [data, setData] = useState(() => {
    const item = localStorage.getItem(key)
    if (!item) return ""
    return JSON.parse(item)
  })

  // 2.监听data改变, 一旦发生改变就存储data最新值
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(data))
  }, [data])

  // 3.将data/setData的操作返回给组件, 让组件可以使用和修改值
  return [data, setData]
}


export default useLocalStorage
在组件中使用:
import React, { memo } from 'react'
import { useEffect } from 'react'
import { useState } from 'react'
import useLocalStorage from './hooks/useLocalStorage'

const App = memo(() => {
  // 通过key, 直接从localStorage中获取一个数据
  // const [token, setToken] = useState(localStorage.getItem("token"))
  // useEffect(() => {
  //   localStorage.setItem("token", token)
  // }, [token])
  const [token, setToken] = useLocalStorage("token")
  function setTokenHandle() {
    setToken("james")
  }

  // const [avatarUrl, setAvatarUrl] = useState(localStorage.getItem("avatarUrl"))
  // useEffect(() => {
  //   localStorage.setItem("avatarUrl", avatarUrl)
  // }, [avatarUrl])
  const [avatarUrl, setAvatarUrl] = useLocalStorage("avatarUrl")
  function setAvatarUrlHandle() {
    setAvatarUrl("http://www.james.com/cba.png")
  }

  return (
    <div className='app'>
      <h1>App Root Component: {token}</h1>
      <button onClick={setTokenHandle}>设置token</button>
      <h1>avatarURL: {avatarUrl}</h1>
      <button onClick={setAvatarUrlHandle}>设置新头像地址</button>
    </div>
  )
})

export default App


redux hooks 


useId 


SSR同构应用 


Hydration 


useId的作用 

import React, { memo, useId, useState } from 'react'

const App = memo(() => {
  const [count, setCount] = useState(0)

  const id = useId()
  console.log(id)

  return (
    <div>
      <button onClick={e => setCount(count+1)}>count+1:{count}</button>

      <label htmlFor={id}>
        用户名:<input id={id} type="text" />
      </label>
    </div>
  )
})

export default App

useTransition 

js:

import { faker } from '@faker-js/faker';

const namesArray = []

for (let i = 0; i < 10000; i++) {
  namesArray.push(faker.name.fullName())
}

export default namesArray
组件:
import React, { memo, useState, useTransition } from 'react'
import namesArray from './namesArray'

const App = memo(() => {
  const [showNames, setShowNames] = useState(namesArray)
  const [ pending, startTransition ] = useTransition()

  function valueChangeHandle(event) {
    startTransition(() => {
      const keyword = event.target.value
      const filterShowNames = namesArray.filter(item => item.includes(keyword))
      setShowNames(filterShowNames)
    })
  }

  return (
    <div>
      <input type="text" onInput={valueChangeHandle}/>
      <h2>用户名列表: {pending && <span>data loading</span>} </h2>
      <ul>
        {
          showNames.map((item, index) => {
            return <li key={index}>{item}</li>
          })
        }
      </ul>
    </div>
  )
})

export default App

useDeferredValue 

 

import React, { memo, useState, useDeferredValue } from 'react'
import namesArray from './namesArray'

const App = memo(() => {
  const [showNames, setShowNames] = useState(namesArray)
  const deferedShowNames = useDeferredValue(showNames)

  function valueChangeHandle(event) {
    const keyword = event.target.value
    const filterShowNames = namesArray.filter(item => item.includes(keyword))
    setShowNames(filterShowNames)
  }

  return (
    <div>
      <input type="text" onInput={valueChangeHandle}/>
      <h2>用户名列表: </h2>
      <ul>
        {
          deferedShowNames.map((item, index) => {
            return <li key={index}>{item}</li>
          })
        }
      </ul>
    </div>
  )
})

export default App

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值