React Hooks

100 阅读14分钟

1. 认识 Hooks

1.1 为什么需要 hooks

它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)

1.1.1 函数式组件的缺点以及类组件的优点

  • class组件可以定义自己的state,用来保存组件自己内部的状态
    • 函数式组件不可以,因为函数每次调用都会产生新的临时变量;
  • class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑
    • 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次;
    • 函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;
  • class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等;
    • 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次;

1.1.2 类组件的缺点

  • 复杂组件变得难以理解:
    • 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂
    • 比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在 componentWillUnmount中移除);
    • 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;
  • 组件复用状态很难:
    • 状态的复用我们需要通过高阶组件
    • redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用;
    • 或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,代码就会存在很多嵌套;
    • 这些代码让我们不管是编写和设计上来说,都变得非常困难;

1.2 使用 hooks

// App.jsx
import React, { PureComponent } from "react"
import CounterClass from "./CounterClass"
import CounterHooks from "./CounterHooks"

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

export default App

// CounterClass.jsx
import React, { PureComponent } from "react"

export class CounterClass extends PureComponent {
  constructor() {
    super()

    this.state = {
      counter: 0
    }
  }
  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }import { useState } from "react"
import { memo } from "react"

function CounterHooks(props) {
  const [counter, setCounter] = useState(0)

  return (
    <div>
      <h2>Counterhooks Page: {counter}</h2>
      <button onClick={e => setCounter(counter + 1)}>+1</button>
      <button onClick={e => setCounter(counter - 1)}>-1</button>
    </div>
  )
}

export default memo(CounterHooks)

  decrement() {
    this.setState({
      counter: this.state.counter - 1
    })
  }
  render() {
    const { counter } = this.state
    return (
      <div>
        <h2>CounterClass Page: {counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <button onClick={e => this.decrement()}>-1</button>
      </div>
    )
  }
}

export default CounterClass

// CounterHooks.jsx
import { useState } from "react"
import { memo } from "react"

function CounterHooks(props) {
  const [counter, setCounter] = useState(0)

  return (
    <div>
      <h2>Counterhooks Page: {counter}</h2>
      <button onClick={e => setCounter(counter + 1)}>+1</button>
      <button onClick={e => setCounter(counter - 1)}>-1</button>
    </div>
  )
}

export default memo(CounterHooks)

1.3 useState 解析

  • useState 是一个函数,返回两个参数:

    • 参数一: useState 的包裹值;
    • 参数二: 修改包裹值的函数方法;
  • 使用规则:

    • 只能在最外层调用 Hook。不能在循环、条件判断或者子函数中调用。

1.4 认识 useState

  • useState会帮助我们定义一个 state 变量,useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。
    • 一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留
    • useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为undefined)。
    • useState的返回值是一个数组,我们可以通过数组的解构,来完成赋值会非常方便。

1.5 useEffect

1.5.1 修改标题-class实现

import React, { PureComponent } from "react"

export class App extends PureComponent {
  constructor() {
    super()

    this.state = {
      counter: 0
    }
  }
  componentDidUpdate() {
    document.title = this.state.counter
  }
  render() {
    const { counter } = this.state
    return (
      <div>
        <h2>App Page: {counter}</h2>
        <button onClick={e => this.setState({ counter: counter + 1 })}>toggle</button>
      </div>
    )
  }
}

export default App

1.5.2 函数实现-useEffect

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

const App = memo(() => {
  const [counter, setCounter] = useState(100)
  useEffect(() => {
    // 当前传入的回调函数会在组件被渲染完成之后,自动执行
    // 网络请求/DOM操作(修改标题)/时间监听
    document.title = counter
  })
  return (
    <div>
      <h2>App Page: {counter}</h2>
      <button onClick={e => setCounter(counter + 1)}>toggle</button>
    </div>
  )
})

export default App

1.5.3 useEffect取消事件监听

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

const App = memo(() => {
  const [counter, setCounter] = useState(100)
  // 负责告知react,在执行完当前组件渲染之后要执行的副作用代码
  useEffect(() => {
    // 监听事件
    console.log("监听redux数据变化,监听eventBus中的why事件")

    return () => {
      console.log("取消监听redux中数据变化,取消监听eventBus中的why事件")
    }
  })
  return (
    <div>
      <h2>App Page: {counter}</h2>
      <button onClick={e => setCounter(counter + 1)}>toggle</button>
    </div>
  )
})

export default App

1.5.4 函数组件中可以包含多个 useEffect 函数

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

  return () => {}
})

useEffect(() => {
  // 2. 对redux中数据变化监听
  console.log("监听redux数据变化")

  return () => {
    // 取消redux中数据的监听
  }
})

useEffect(() => {
  // 3. 监听eventBus中的why事件
  console.log("监听eventBus中的why事件")

  return () => {
    // 取消eventBus中的why事件
  }
})

1.5.5 useEffect 两个参数值

  • 默认情况下,useEffect的回调函数在每次渲染时都要重新执行,但是会导致两个问题:

    • 有些代码只执行一次,类似于componentDidMount和componentWillUNmount、网络请求、订阅和取消订阅;
    • 多次执行也会导致一定的性能问题;
  • 决定useEffect在何时执行

    • 参数一:执行的回调函数
    • 参数二:useEffect在哪些state发生变化时,才重新执行
  • 如果一个函数不希望依赖任何的内容时,也可以传入一个空的数组[]

1.6 useContext

// index.js
root.render(
  // <React.StrictMode>
  <UserContext.Provider value={{ name: "why", level: 99 }}>
    <ThemeContext.Provider value={{ color: "red", size: 30 }}>
      <App />
    </ThemeContext.Provider>
  </UserContext.Provider>
  // </React.StrictMode>
)

// content/index.js
import { createContext } from "react"

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

export { UserContext, ThemeContext }

// content/App.jsx
import React, { memo } from "react"
import { useContext } from "react"
import { ThemeContext, UserContext } from "./content"

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

1.7 useReducer

import React, { memo } from "react"
import { 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
  }
}

const App = memo(() => {
  // const [count, setCount] = useState(0)
  const [state, dispatch] = useReducer(reducer, { counter: 0 })
  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

1.8 useCallback & useMemo

1.8.1 初步认识 useCallback

import React, { memo } from "react"
import { useCallback } from "react"
import { useState } from "react"

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

  // 这样使用useCallback并没有对setCount做优化
  // 只是每次在外层定义改成了将其作为参数每次定义
  const increment = useCallback(() => {
    setCount(count + 1)
  })
  return (
    <div>
      <h2>当前计数: {count}</h2>
      <button onClick={increment}>+1</button>
    </div>
  )
})

export default App

1.8.2 useCallback 性能优化的点

  • 当需要将一个函数传递给子组件时,最好使用useCallback进行优化,将优化之后的函数,传递给子组件
  • 通常使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存;
  • 缺点:
    • 使用 useCallback,稍不注意就会发生闭包陷阱问题:由于 useCallback 中的函数是有记忆的,在每次修改了值之后,useCallback 使用的都是旧值,所以即使值发生变化,useCallback 中的函数执行体也并未真正的执行,这样一来,就会导致渲染值与真实的值产生冲突。
// Child
// props 中的属性发生改变时,组建本身就会被重新渲染
const WZHome = memo(function (props) {
  const { increment } = props
  console.log("WZHome被重新渲染")

  return (
    <div>
      <button onClick={increment}>increment+1</button>
    </div>
  )
})

// Father
const App = memo(() => {
  const [count, setCount] = useState(0)
  const [message, setMessage] = useState("react")

  // 这样使用useCallback并没有对setCount做优化
  // 只是每次在外层定义改成了将其作为参数每次定义
  // 闭包陷阱:useCallback
  const increment = useCallback(() => {
    console.log("increment")
    setCount(count + 1)
  }, [count])
  // const increment = () => {
  //   console.log("increment")
  //   setCount(count + 1)
  // }
  return (
    <div>
      <h2>当前计数: {count}</h2>
      <button onClick={increment}>+1</button>

      <WZHome increment={increment} />

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

1.8.3 useCallback 进一步优化

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

1.8.4 useMemo 的使用

  • useMemo 的目的也是为了进行性能的优化。在依赖不变的情况下,多次定义的时候,返回的值都是相同的。
  • 使用场景:
    • 进行大量的计算操作,并非每次渲染时都重新计算
    • 对子组件传递相同内容的对象时,使用 useMemo 进行性能的优化
import React, { memo, useMemo, useState } from "react"

function calcNumTotal(num) {
  let result = 0
  for (let i = 1; i <= num; i++) {
    result += i
  }
  return result
}

const HelloWorld = memo(function (props) {
  console.log("Hello World 重新渲染~")
  return (
    <div>
      <h1>Hello World</h1>
    </div>
  )
})

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() {}
  // useCallback 是当传入的函数中依赖的值发生变化,返回一个新的函数
  // const increment = useCallback(fn, [])
  // useMemo 是在传入一个回调函数时,并且返回一个新的函数,二者仅参数及返回值的区别
  // const increment2 = useMemo(() => fn, [])

  // 4. 使用useMemo对子组件渲染进行优化
  // 字面量方法每次都会重新创建一个新的对象, 所以子组件会重新渲染
  // const info = { name: "lwz", age: 18 }
  const info = useMemo(() => ({ name: "lwz", age: 18 }), [])

  return (
    <div>
      <h2>count的值:{result}</h2>
      <div>当前count的值:{count}</div>
      <button onClick={e => setCount(count + 1)}>+1</button>

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

export default App

1.9 useRef 的使用

  • useRef 返回一个 ref 对象,返回的 ref 对象在组件的整个生命周期中保持不变
  • 常见用法:
    • 引入 DOM(或者组件,但是需要是class组件)元素;
    • 保存一个数据,这个对象在整个生命周期中可以保存不变
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)
  // useRef 在内存中保存的是同一个对象,与字面量方法创建的对象不同
  const nameRef = useRef()
  console.log(obj === nameRef)
  obj = nameRef

  // 通过 useRef 解决闭包陷阱
  const countRef = useRef()
  countRef.current = count
  const increment = useCallback(() => {
    // setCount(countRef.current + 1)
    // 下面这种方式会产生闭包陷阱问题
    setCount(count + 1)
  }, [])
  return (
    <div>
      <h2>Hello Wolrd: {count}</h2>
      <button onClick={e => setCount(count + 1)}>+1</button>
      <button onClick={increment}>+1</button>
    </div>
  )
})

export default App

1.10 useImperativeHandle 的使用

  • 解决 forwardRef 暴露给父组件过多的接口可能会导致不可预测的问题
import React, { forwardRef, memo } from "react"
import { useImperativeHandle } from "react"
import { useRef } from "react"

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

    // 子组件对父组件传进来的 ref 进行操作
    useImperativeHandle(ref, () => {
      return {
        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 getDom() {
    inputRef.current.focus()
    inputRef.current.setValue("hahaha")
  }

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

export default App

1.10 useLayoutEffect 和 useEffect 的对比

  • useEffect 会在渲染的内容更新到 DOM 上再执行,不会阻塞 DOM 的更新;
  • useLayoutEffect 会在渲染的内容更新到 DOM 上之前进行执行,会阻塞 DOM 的更新;
import React, { memo } from "react"
import { useLayoutEffect } from "react"
import { useEffect } from "react"
import { useState } from "react"

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

  // useLayoutEffect 渲染时机是在DOM 渲染到页面之前的执行的
  useLayoutEffect(() => {
    console.log("useLayoutEffect Render")
  })

  useEffect(() => {
    console.log("useEffect Render")
  })

  console.log("App Render")
  return (
    <div>
      <h2>当前计数:{count}</h2>
      <button onClick={e => setCount(count + 1)}>+1</button>
    </div>
  )
})

export default App

1.11 自定义Hook

1.11.1 案例一:打印生命周期

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

function useLogLife(cName) {
  useEffect(() => {
    console.log(`${cName}被创建`)
    return () => {
      console.log(`${cName}被销毁`)
    }
  })
}

const Home = memo(() => {
  useLogLife("Home")
  return <h1>Home Page</h1>
})

const About = memo(() => {
  useLogLife("About")
  return <h1>About Page</h1>
})

const App = memo(() => {
  useLogLife("App")
  const [isShow, setShow] = useState(true)
  return (
    <div>
      <h1>App Page</h1>
      <button onClick={e => setShow(!isShow)}>toggle</button>
      {isShow && (
        <div>
          <Home />
          <About />
        </div>
      )}
    </div>
  )
})

export default App

1.11.2 自定义Context

// src/index.js
root.render(
  // <React.StrictMode>
  <UserContext.Provider value={{ name: "why", level: 99 }}>
    <TokenContext.Provider value={"lwzToken"}>
      <App />
    </TokenContext.Provider>
  </UserContext.Provider>
  // </React.StrictMode>
)

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

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

  return [user, token]
}

export default useUserToken

// App.jsx
const App = memo(() => {
  const [user, token] = useUserToken()
  return (
    <div>
      <h1>
        App Page: {user.name}-{user.level}-{token}
      </h1>
      <Home />
      <About />
    </div>
  )
})

1.11.3 获取滚动位置

// hooks/useScrollHandle.js
import { useEffect, useState } from "react"

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

  useEffect(() => {
    function scrollToBottom() {
      setScrollX(window.scrollX)
      setScrollY(window.scrollY)
    }
    window.addEventListener("scroll", scrollToBottom)
    return () => {
      window.removeEventListener("scroll", scrollToBottom)
    }
  }, [])

  return [scrollX, scrollY]
}

export default useScrollHandle

// Home.jsx
const Home = memo(() => {
  const [scrollX, scrollY] = useScrollHandle()
  return (
    <h1>
      Home Page: {scrollX}-{scrollY}
    </h1>
  )
})

1.11.4 存储到 localStorage

// hooks/useLocalStorage.js
import { useEffect, 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

// App.jsx
const App = memo(() => {
  const [token, setToken] = useLocalStorage("token")
  function setTokenHandle() {
    setToken("james")
  }
  return (
    <div>
      <h1>App Page: {token}</h1>
      <button onClick={setTokenHandle}>change Token</button>
      <Home />
      <About />
    </div>
  )
})

1.12 redux 中的 hooks 使用

1.12.1 redux_connect

// store/counter.js
import { createSlice } from "@reduxjs/toolkit"

const counterSlice = createSlice({
  name: "counter",
  initialState: {
    count: 99
  },
  reducers: {
    addNumberAction: (state, { payload }) => {
      state.count += payload
    },
    subNumberAction: (state, { payload }) => {
      state.count -= payload
    }
  }
})

export const { addNumberAction, subNumberAction } = counterSlice.actions
export default counterSlice.reducer

// store/index.js
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./modules/counter"

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})

export default store

// src/index.js
import { Provider } from "react-redux"
import store from "./12_redux中的hooks/store"

<Provider store={store}>
  <App />
</Provider>

// App.jsx
import React, { memo } from "react"
import { connect } from "react-redux"
import { addNumberAction, subNumberAction } from "./store/modules/counter"

const App = memo(props => {
  const { count, addNumber, subNumber } = props

  function addNumberHandle(num, isAdd = true) {
    isAdd ? addNumber(num) : subNumber(num, false)
  }
  return (
    <div>
      <h2>当前计数: {count}</h2>
      <button onClick={e => addNumberHandle(1)}>+1</button>
      <button onClick={e => addNumberHandle(6)}>+6</button>
      <button onClick={e => addNumberHandle(6, false)}>-6</button>
    </div>
  )
})

const mapStateToProps = state => ({
  count: state.counter.count
})
const mapActionToProps = dispatch => ({
  addNumber: num => dispatch(addNumberAction(num)),
  subNumber: num => dispatch(subNumberAction(num))
})
export default connect(mapStateToProps, mapActionToProps)(App)

1.12.2 redux_hooks

import React, { memo } from "react"
import { useDispatch } from "react-redux"
import { useSelector } from "react-redux"
import { addNumberAction, subNumberAction } from "./store/modules/counter"

const App = memo(props => {
  // 1. 使用useSelector将redux中store的数据映射到组件内
  const { count } = useSelector(state => ({
    count: state.counter.count
  }))

  // 2. 直接使用dispatch派发action事件
  const dispatch = useDispatch()
  function addNumberHandle(num, isAdd = true) {
    isAdd ? dispatch(addNumberAction(num)) : dispatch(subNumberAction(num, false))
  }
  return (
    <div>
      <h2>当前计数: {count}</h2>
      <button onClick={e => addNumberHandle(1)}>+1</button>
      <button onClick={e => addNumberHandle(6)}>+6</button>
      <button onClick={e => addNumberHandle(6, false)}>-6</button>
    </div>
  )
})

export default App

1.13 useSelector & useDispatch

  • useSelector 的作用是将 state 映射到组件中:

    • 参数一:将state映射到需要的数据中;
    • 参数二:shallowEqual 可以进行比较来决定是否组件重新渲染
  • useDispatch 直接获取 dispatch 函数,之后在组建中直接使用即可

// Home.jsx
// memo高阶组件包裹起来的组件有对应的特点: 只有props发生改变时, 才会重新渲染
const Home = memo(props => {
  const { message } = useSelector(
    state => ({
      message: state.counter.message
    }),
    shallowEqual
  )

  const dispatch = useDispatch()
  function changeMessageHandle() {
    dispatch(changeMessageAction("你好啊, 师姐!"))
  }

  console.log("Home render")

  return (
    <div>
      <h2>Home: {message}</h2>
      <button onClick={e => changeMessageHandle()}>修改message</button>
    </div>
  )
})

// App.jsx
const App = memo(props => {
  // 1.使用useSelector将redux中store的数据映射到组件内
  const { count } = useSelector(
    state => ({
      count: state.counter.count
    }),
    shallowEqual
  )

  // 2.使用dispatch直接派发action
  const dispatch = useDispatch()
  function addNumberHandle(num, isAdd = true) {
    if (isAdd) {
      dispatch(addNumberAction(num))
    } else {
      dispatch(subNumberAction(num))
    }
  }

  console.log("App render")

  return (
    <div>
      <h2>当前计数: {count}</h2>
      <button onClick={e => addNumberHandle(1)}>+1</button>
      <button onClick={e => addNumberHandle(6)}>+6</button>
      <button onClick={e => addNumberHandle(6, false)}>-6</button>

      <Home />
    </div>
  )
})

1.14 useId

  • 早期的SSR的页面:JSP。只有一个index.html,保存在后端,在请求域名或者网址的时候请求到的就是这一个页面,而这个页面中包含所有的内容:html、css、js

  • 现在的方式:单页面富应用SPA。

    • 缺点:
      • 不利于SEO优化:搜索引擎优化
        • 搜索引擎,如百度浏览器,往往只会爬取单页面index.html,而这个页面中仅包含很少的内容,因此包含的关键字非常少,用户在输入关键字进行匹配的时候很难匹配到公司的产品,所以在这个方面来说弊端非常大;
      • 首屏的渲染速度
        • 由于这个index.html中包含js文件,而这个文件内容非常大,所以浏览器在下载并解析执行js文件的时候会非常慢,首屏渲染速度会很慢
  • 如果是后台管理系统,往往是公司内部或者特定的公司使用,使用场景非常有限,此外,现在的浏览器解析速度很快,所以首屏的渲染速度与之前的SSR渲染也不会相差很多。

  • SSR(Server Side Rendering,服务端渲染),指的是页面在服务器端已经生成了完成的HTML页面结构,不需要浏览器解析;

  • 对应的是CSR(Client Side Rendering,客户端渲染),我们开发的SPA页面通常依赖的就是客户端渲染;

  • SSR同构应用

    • **同构:**一套代码可以在服务端运行,又可以在客户端运行。
    • 同构是一种SSR的形态,是现在SSR的一种表现形式。
      • 当用户发出请求时,现在服务器通过SSR渲染出首页的内容
      • 但是对应的代码同样可以在客户端被执行
      • 执行的目的包括事件绑定以及其他页面切换时也可以在客户端被渲染
  • Hydration

    • 是同构应用的一个步骤
    • 在进行SSR时,我们的页面会呈现为HTML。
      • 但仅HTML不足以使页面具有交互性。例如,浏览器端JavaScript为0的页面是不能交互的(没有JavaScript事件处理程序来响应用户操作,例如单击按钮)
      • 为了使我的页面具有交互性,除了在Node.js中将页面呈现为HTML之外,我们的UI框架(Vue/React/...)还在浏览器中加载和呈现页面。(它创建页面的内部表示,然后将内部映射到我们在Node.js中呈现的HTML的DOM元素)
  • useId的作用

    • useId是一个用于生成横跨服务器端和客户端的稳定的唯一ID的同时避免hydration不匹配的hook。
import React, { memo } from "react"
import { useId } from "react"
import { 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"></input>
      </label>
    </div>
  )
})

export default App

1.15 useTransition

  • 作用:告诉 react 对于某部分任务的更新优先级较低,可以稍后进行更新。
// namesArray.js
import { faker } from "@faker-js/faker"

const namesArray = Array.from({ length: 1000 }).map(item => faker.name.firstName())

export default namesArray

// App.jsx
import React, { memo, useState, useTransition } from "react"
import namesArray from "./namesArray"

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

  const [pending, startTransition] = useTransition()

  const valueChangeHandle = e => {
    startTransition(() => {
      const keyWord = e.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) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  )
})

export default App

1.16 useDeferredValue 的使用

  • 效果与 useTransation 效果相同,可以更新延迟
import React, { memo, useState, useDeferredValue } from "react"
import namesArray from "./namesArray"

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

  const valueChangeHandle = e => {
    const keyWord = e.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) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  )
})

export default App