useState


语法:
const [n, setN] = React.useState(0);

0是n的默认值,setN是操作n的函数

setN: setN(i=>i+1)

  1. 我们以为setN会改变n,但是事实往往出乎意料,n并不会变,他是将改变后的数据存到一个数据里面(具体在哪里我们先定为x),而不是直接赋值去改变n
  2. setN一定会触发组件的重新渲染

useState:

  1. useState肯定会从x那里,读取到n的最新值

x:

  1. 每个组件都有自己的x,这个x我们命名为state

useRef


1.如果你需要一个值,在组件不断render的时候保持不变

// 在组件不断render的时候,count的值变了,但是页面显示不变
// 每次创建的是新的count,而不是用上一次的count
  const count = useRef(0)
    const onClick = () => {
    count.current += 1
    console.log(count)
  }

image.png
2.如果你想要改变这个值

  // 每次页面也会发生变化,展示对应的值
  const [_, set_] = useState(null)
  const count = useRef(0)
  const onClick = () => {
    count.current += 1
    set_(Math.random())
    console.log(count)
  }

image.png
3.假设我们有这样一个需求,记录点击按钮的次数

// 使用useRef记录改变n多少次
const count = useRef(0)
useEffect(() => {
    count.current += 1
    console.log(count)
})

image.png

useContext


const themeContext = React.createContext(null);

function App() {
  const [theme, setTheme] = React.useState("red");
  return (
    // themeContext.Provider 创建一个局部作用域
    // 其中里面的所有组件都可以使用setTheme这个函数
    <themeContext.Provider value={{ theme, setTheme }}>
      <div className={`App ${theme}`}>
        <p>{theme}</p>
        <div>
          <ChildA />
        </div>
        <div>
          <ChildB />
        </div>
      </div>
    </themeContext.Provider>
  );
}

function ChildA() {
  // ChildA这个子组件内部想使用父组件setTheme函数的方式
  const { setTheme } = React.useContext(themeContext);
  return (
    <div>
      <button onClick={() => setTheme("red")}>red</button>
    </div>
  );
}

useReducer


创建初始值

const store = {
  user: null,
  books: null,
  movies: null,
}

创建所有操作的reducer

function reducer(state, action) {
  switch (action.type) {
    case 'setUser':
      return { ...state, user: action.user }
    case 'setBooks':
      return { ...state, books: action.books }
    case "setMovies":
      return { ...state, movies: action.movies }
    default:
      throw new Error();
  }
}

使用React提供的createContext创建Context

// 使用React提供的createContext API,将子组件套住
const Context = createContext(null);

function App() {
  return (
    // 套住子组件
    <Context.Provider>
        <User />
        <hr />
    </Context.Provider>
  );
}
// 子组件User
function User() {
  return (
    <div>
      <h1>个人信息</h1>
    </div>
  )
}

创建对数据读写的API

function App() {
  // 使用useReducer对数据进行读写操作
  const [state, dispatch] = useReducer(reducer, store)
  return (
    // 套住子组件 value是将读和写的API赋值给子组件
    <Context.Provider value={{ state, dispatch }}>
      <User />
      <hr />
    </Context.Provider>
  );
}

子组件中使用useContext读取数据

function User() {
  // 子组件中使用父组件数据的方式
  const { state, dispatch } = useContext(Context)
  // 使用useEffect,只在初次进入的时候才会发起请求
  // 避免每次render的时候都会请求
  // dispatch会改写初始化的数据
  useEffect(() => {
    ajax("/user").then(user => {
      dispatch({ type: 'setUser', user })
    });
  }, []);
  return (
    <div>
      <h1>个人信息</h1>
      <div>name: {state.user ? state.user.name : ""}</div>
    </div>
  )
}

useEffect(render之后执行)


1.当useEffect,第二个参数为空数组时

  // 第一个参数是执行函数
  // 第二个参数如果是个空数组,只会在第一次render之后执行一次
  // 后面再次render也不会执行
  useEffect(() => {
    console.log('第一次render之后执行!')
  }, [])

2.当useEffect,第二个参数不传时

  // 第一个参数是执行函数
  // 第二个参数不传, 后面每次render都会执行
  useEffect(() => {
    console.log('每次render之后执行!')
  })

2.当useEffect,第二个参数传需要变化的值时

  // 第一个参数是执行函数
  // 第二个参数传需要改变的值的时候(要放在数组里)
  // 当n变化了,函数才会执行
  useEffect(() => {
    console.log('每次render之后执行!')
  }, [n])

useLayoutEffect(DOM完毕之后,render之前执行)


使用的方式和useEffect一样,时间顺序不一样

memo


看下面这个图:

  1. 当我每次点击按钮的时候,Child函数组件都会执行
  2. 但是Child并没用用到n这个值

image.png

  1. 用memo封装一下,n的值在改变的时候,Child函数组件就不会执行了
  2. 只有m变化的时候才执行

image.png

  1. 有问题,当我们给子组件传递函数的时候,我们点击按钮
  2. Child函数组件还是执行了

image.png
如何解决这样的问题?用到下面的API

useMemo


  1. 将传递给子组件的函数,放在useMemo中,第二个参数是要改变的值m
  2. 这样就只有当m改变的时候,Child函数组件才会重新执行了

image.png

  1. 是不是觉得useMemo很别扭,因为他里面是函数作为参数,返回的还是一个函数,另一个API可以解决这样的问题: useCallback
  2. useCallback和useMemo是一样的
  const onClickChild = useCallback(() => {
    console.log(m)
  }, [m])

forwardRef


  1. 在函数式的组件中是无法使用ref获取子组件的
  2. 想要获取子组件使用forwardRef这个API
  3. 将子组件Button2用forwardRef包裹起来
  4. 这样就能获取到button的DOM
function App() {
  const buttonRef = useRef(null)
  return (
    <div>
      <Button3 ref={buttonRef}>按钮</Button3>
    </div>
  );
}
function Button2(props, ref) {
  console.log(props)
  console.log(ref)
  return (
    <button className='red' ref={ref} {...props}>按钮</button>
  );
}
const Button3 = React.forwardRef(Button2)

image.png

自定义Hook


用一段代码来表示他的NB之处,下面这段代码useList是封装在了另一个文件里面

  1. 假设./hooks/useList这个文件里面是对list的增,删,改,查
import useList from "./hooks/useList"
function App() {
  const { list, setList } = useList()
  return (
    <div>
      <h1>List</h1>
      {list ? (
        <ol>
          {list.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ol>
      ) : ("加载中.....")}
    </div>
  );
}
  1. 这个是./hooks/useList这个文件内部
  2. 这个文件只有一个假的ajax
  3. 还可以将增,改,删等操作封装进去,这就会将操作和展示分开
  4. 模块化会使代码格外清晰,这就是他的NB之处
import {
    useState,
    useEffect
} from "react"

const useList = () => {
    const [list,
        setList
    ] = useState(null)
    useEffect(() => {
        ajax("/list").then(list => {
            setList(list)
        })
    }, [])
    return {
        list,
        setList
    }
}
export default useList;

function ajax(n) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([{
                id: 1,
                name: "Frank"
            }, {
                id: 2,
                name: "Sun"
            }, {
                id: 3,
                name: "Jack"
            }])
        }, 2000)
    })
}

大V是个啥
10 声望1 粉丝

喜欢前端,不断学习的自我驱动型!!!