一、开发环境及创建项目

# 一、开发环境及创建项目

Node : 17.3.0

创建项目:npx create-react-app my-app

进入项目:cd my-app

启动项目:npm start

# 二、useState

useState方法:接受一个参数,可以为默认值,也可以为一个函数。

import logo from './logo.svg';
import './App.css';
import React,{ useState } from 'react'

function App() {
    const [data,setData] = useState([1,2,3,4,5])
    return (
            <div className="App">
                {
                    data.map((item,index) => <div key={index}>{item+1}</div>)
        }
    </div>
  );
}

export default App;

# 三、useEffect

通过 useEffect 副作用,mock请求一个接口数据

import React, { useEffect, useState } from 'react'
// 模拟数据接口,3 秒钟返回数据。
const getList = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([6, 7, 8, 9, 10])
    }, 3000)
  })
}

function App() {
  const [data, setData] = useState([1, 2, 3, 4, 5])

  useEffect(() => {
    (async () => {
      const data = await getList()
      console.log('data', data)
      setData(data)
    })()
  })
  return (
    <div className="App">
      {
        data.map((item, index) => <span key={index}>{item}</span>)
      }
    </div>
  )
}

export default App

函数组件默认进来之后,会执行 useEffect 中的回调函数,但是当 setData 执行之后,App 组件再次刷新,刷新之后会再次执行 useEffect 的回调函数,这便会形成一个可怕的死循环,回调函数会一直被这样执行下去。

所以我们如果传一个空数组 [],则该副作用只会在组件渲染的时候,执行一次。

# 三、自定义hook

抽离成一个自定义 hook,方便在多个地方调用,新建 useApi.js

// useApi.js
import React, { useEffect, useState } from 'react'
// 模拟请求
const getList = (query) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('query', query)
      resolve([6, 7, 8, 9, 10])
    }, 3000)
  })
}
// 自定义 hook
const useApi = () => {
  const [data, setData] = useState([1, 2, 3, 4, 5])
  const [query, setQuery] = useState('')

  useEffect(() => {
    (async () => {
      const data = await getList(query)
      // console.log('data', data)
      setData(data)
    })()
  }, [query])

  // 将data数据 设置请求参数的方法抛出
  return [{ data }, setQuery];
}

export default useApi
// App.js
import './App.css';
import React, { useState, useEffect } from 'react'
import useApi from './useApi';

function App() {
    const [{data},setQuery] = useApi()
    return (
        <div className="App">
            {data.map((item, index) => <span key={index}>{item}</span>)}
            <input onChange={(e) => setQuery(e.target.value)} type='text' placeholder='...' />
        </div>
    );
}

export default App;

自定义hook,可以将请求逻辑提取出来公用,Class类组件所不能做到的。

# 四、useMemo

修改 App.jsx,在内部新增一个子组件,子组件接收父组件传进来的一个对象,作为子组件的 useEffect 的第二个依赖参数。

import './App.css';
import React, { useState, useEffect } from 'react'

function Child({ data }) {
    useEffect(() => {
        console.log("search condition", data)
    }, [data])
    return <div>child</div>
}

function App() {
    // const [{data},setQuery] = useApi()
    const [name, setName] = useState('')
    const [phone, setPhone] = useState('')
    const [kw, setKw] = useState('')

    const data = {
        name,
        phone
    }
    return (
        <div className="App">
            <input onChange={(e) => setName(e.target.value)} type='text' placeholder='name' />
            <input onChange={(e) => setPhone(e.target.value)} type='text' placeholder='phone' />
            <input onChange={(e) => setKw(e.target.value)} type='text' placeholder='key word' />
            <Child data={data}/>
        </div>
    );
}

export default App;

我们只监听了 namephone 两个参数,但是我们修改关键词输入框,还是会有打印结果。

子组件并没有监听 kw 的变化,但是结果却是子组件也被触发渲染了。原因其实是我们在父组件重新 setKw 之后,data 值和未作修改 kw 前的值已经不一样了。data 的值并没有变化,为什么说它已经不一样了呢?

通过 useMemodata 包装一下,告诉 data 它需要监听的值。

import './App.css';
import React, { useState, useEffect,useMemo } from 'react'
import useApi from './useApi';

function Child({ data }) {
    useEffect(() => {
        console.log("search condition", data)
    }, [data])
    return <div>child</div>
}

function App() {
    // const [{data},setQuery] = useApi()
    const [name, setName] = useState('')
    const [phone, setPhone] = useState('')
    const [kw, setKw] = useState('')

    const data = useMemo(()=>({
        name,
        phone
    }),[name,phone])
    
    return (
        <div className="App">
            <input onChange={(e) => setName(e.target.value)} type='text' placeholder='name' />
            <input onChange={(e) => setPhone(e.target.value)} type='text' placeholder='phone' />
            <input onChange={(e) => setKw(e.target.value)} type='text' placeholder='key word' />
            <Child data={data}/>
        </div>
    );
}

export default App;

useMemo 的作用,它相当于把父组件需要传递的参数做了一个标记,无论父组件其他状态更新任何值,都不会影响要传递给子组件的对象。

# 五、useCallback

useCallback 也是和 useMemo 有类似的功能


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

function Child({ callback }) {
  useEffect(() => {
    callback()
  }, [callback])

  return <div>子组件</div>
}


function App() {

  const [name, setName] = useState('')
  const [phone, setPhone] = useState('')
  const [kw, setKw] = useState('')

  const callback = () => {
    console.log('我是callback')
  }

  return (
    <div className="App">
      <input onChange={(e) => setName(e.target.value)} type="text" placeholder='请输入姓名' />
      <input onChange={(e) => setPhone(e.target.value)} type="text" placeholder='请输入电话' />
      <input onChange={(e) => setKw(e.target.value)} type="text" placeholder='请输入关键词' />
      <Child callback={callback} />
    </div>
  )
}

export default App

useCallback 的第二个参数同 useEffectuseMemo 的第二个参数,它是用于监听你需要监听的变量,如在数组内添加 namephonekw 等参数,当改变其中有个,都会触发子组件副作用的执行。

useMemouseCallback,都能为「重复渲染」这个问题,提供很好的帮助。