小白菜笔记--React(三)函数组件


写在前面:学习 React 要时刻告诉自己就是在写 JavaScript

函数组件的创建方式

// 箭头函数形式
const Hello = (props) => { return <div>{props.message}</div> }
// function 形式
function Hello(props) {
  return <div>{props.message}</div>
}

就是如此,和 JS 函数的创建一样(心里默念学习 React 要时刻告诉自己就是在写 JavaScript

Props 外部数据

就是函数组件的参数

State 和生命周期

没有

那我走? 🤷‍♀️

别急,React v16.8.0 推出 Hooks API,可厉害了,了解下吧~

Hooks

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数,使用它们有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

useState

还记得在 Class 组件中怎么使用 state 的吗

看这个例子 🌰

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

我们通过在构造函数中设置 state 初始值为 { count: 0 } ,当用户点击按钮后,我们通过调用 this.setState() 来增加 state.count。

回顾一下上面函数组件的创建方式,我们似乎无法很方便地向里面添加一些 state 使得 state 变化时能自动更新 UI

这时就该主角 useState 登场啦 😎

import React, { useState } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
       Click me
      </button>
    </div>
  );
}

使用 useState 定义 state 变量返回的是一个有两个值的数组。第一个值是当前的 state,第二个值是更新 state 的函数。

useState() 方法里面唯一的参数就是初始 state。这里声明了一个叫 count 的 state 变量,然后把它设为 0。

通过调用 setCount 来更新当前的 count。

注意事项:

  1. 如果 state 是一个对象,不能部分 setState,因为 setState 不会合并属性
  2. 对于 setState(obj),如果 obj 地址不变,那么 React 就认为数据没有变化

useEffect

useEffect 给函数组件增加了操作副作用的能力。它跟 Class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

来看下 useEffect 的用法

// 模拟 componentDidMount,注意第二个参数
useEffect(() => { /* 只在第一次渲染时会执行 */ }, [])

// 模拟 componentDidUpdate,注意第二个参数
useEffect(() => { /* 任意属性更新就会执行,第一次渲染也会 */ })
useEffect(() => { /* 只有n更新才会执行,第一次渲染也会 */ }, [n])

// 模拟 componentWillUnmount,注意第一个参数的 return 语句
useEffect(() => {
    return () => { /* 执行组件卸载的时候清除操作(保护环境) */ }
})

如果同时存在多个 useEffect,会按照出现次序执行

useLayoutEffect

在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

特点

  • useLayoutEffect 总是比 useEffect 先执行
  • useLayoutEffect 里的任务最好是影响了 Layout
App() ---> 执行 ---> VDOM ---> DOM ---> 改变外观 ---> useEffect
                                    ↑
                              useLayoutEffect

useContext

用法

  1. 声明一个 context 对象
    const MyContext = React.createContext()
  2. 使用 <MyContext.Provider> 圈定作用域
    function App(){
      return (
        <MyContext.Provider value={520}>
            <Childlren />
        </MyContext.Provider>
      )
    }
  3. 在作用域内使用 useContext(MyContext) 获取上下文
    function Chrilren() {
        const value = useContext(MyContext)
        return (
            <div>{ value }</div>
        )
    }

useReducer

使用官网上的例子

// 第 1 步:创建初始值 initialCount
function init(initialCount) {
  return {count: initialCount};
}
// 第 2 步:创建所有操作 reducer(state, action)
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}
function Counter({initialCount}) {
  // 第 3 步:传给 useReducer ,得到读写 API
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        // 第 4 步:调用 dispatch({type: '操作类型'})
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useMemo

使用背景:React 默认有多余的 render

<父组件>
    <子组件 />
</父组件>

当 <父组件> 中 state 变化时, <子组件> 跟着重新渲染,但如果 <子组件> 的 props 没有变化是没必要重新渲染的。

用 <子组件2> = React.memo(<子组件>) 可以解决,但是有 Bug :在 <子组件> 内添加了监听函数后就会失效。原因是函数是对象, <父组件> 重新渲染会导致函数地址不同从而使 props 发生变化。

用 useMemo 包装 <子组件> 内的监听函数。

用法

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

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized

useCallback

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useRef

const refContainer = useRef(initialValue)

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

读取方式:refContainer.current

useRef 常用于访问子组件

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素<input>
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

自定义 Hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook,通常用于封装数据操作。

下面是一个自定义 Hook 示例

// useRecords.jsx
import { useState } from 'react';
export const useRecords = () => {
  const [records, setRecords] = useState([])
  // 一些增删查改的操作
  const findRecord = () => {}
  const addRecord = () => {}
  const deleteRecord = () => {}
  const updateRecord = () => {}
  return {records, addRecord, deleteRecord, findRecord, updateRecord}
}

使用自定义 Hook

function App() {
  const {records} = useRecords()
  // 其他代码
}

文章作者: April-cl
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 April-cl !
  目录