React的函数组件和React Hooks

79 阅读3分钟

0.函数组件的基本结构

const Hello = (props) => {
    return (<div>Hello{ props.message}</div>)
}

function Hello(props){
    return (<div>Hello{ props.message}</div>)
}

两个核心问题

  1. 没有state
  2. 没有生命周期函数

解决方案: React Hooks

React Hooks

1. useState

参考函数式编程的思想,对环境的改变就是副作用

import React, { useEffect } from "react";
import { useState } from "react";
import ReactDOM from "react-dom"
function Hello(props){
    return (<div>Hello{ props.message}</div>)
}

function App () {
    const [count, setCount] = useState(0)
    useEffect(() => {
        console.log(`这是第${count}次点击`);
    })

    return (
        <div>
            <Hello message="123"/>
            <p>Count:{count}</p>
            <button onClick={()=>setCount(i=>i+1)}>+1</button>
        </div>
    )
}


ReactDOM.render(<App />, document.getElementById("root"))

useState这个函数接收的参数是状态的初始值(Initial state),它返回一个数组,这个数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。

对于setCount一定会修改数据count,将+1后的值存入,然后触发App 组件的重新渲染

useState则每次重新渲染时执行,读取count的最新值

多状态声明

  • 每一个函数组件对应一个React节点
  • state使用类似于数组的方案进行存储
  • 每个节点保存自己的state和index-
  • 使用useState是读取state[index]
  • index根据useState出现的顺序来确定的
  • setState修改对应的State,触发重新渲染
  • useState不能出现在条件语句内

布局副作用 uselayoutEffect

useEffect 在浏览器渲染完成后执行

uselayoutEffect在浏览器渲染前执行,早于useEffect

function App () {
    const Re = useRef(null)
    useEffect(() => {
        TweenMax.to(Re.current, 0, { x: 600 })
    },[])
    return (
        <div  className="out">
            <div ref={Re} className="square">square</div>
        </div>
    );
}
body{
    background-color: yellow;
}
.out{
    color: #66FFFF;
    width: 100px;
    height: 100px;
}
.square{
    background-color: orange;
    height: 1000px;
}

会发现中间的方块一闪而过

模拟生命周期函数

基本原则

  1. React首次渲染和之后的每次渲染都会调用一遍useEffect函数。
  2. useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonutcomponentDidUpdate中的代码都是同步执行的。

模拟方案

  1. 模拟componentDidMount
    useEffect(() => {
        console.log("初始化");
    },[])
  1. 模拟componentDidUpdate
    useEffect(() => {
        if(count>0) console.log(`这是第${count}次点击`);
    },[count])
  1. 模拟componentWillUnmount
    useEffect(() => {
        console.log("进入组件");
        return () => {
            console.log("推出组件");
        }
    },[])

传入空数组时代表组件销毁再解绑

2 useReducer

什么是Reducer

reducer其实就是一个函数,这个函数接收两个参数,一个是状态,一个用来控制业务逻辑的判断参数。

function countReducer(state, action) {
    switch(action.type) {
        case 'add':
            return state + 1;
        case 'sub':
            return state - 1;
        default: 
            return state;
    }
}

使用方法

  1. 创建初始值
  2. 编写Reducer,创建所有操作
  3. 传给useReducer,得到所有读写操作
  4. ({"type":操作})调用
const initalCount = {
    count:0
}
const reducer = (state, action) => {
    switch (action.type) {
        case 'add':
            return { count: state.count + 1 };
        case 'sub':
            return { count: state.count - 1 };
        default:
            return {count:state.count};
    }
}
function App () {
    const [state, dispatch] = useReducer(reducer,initalCount)
    const { count } = state
    return (
        <div>
            <h2>现在的分数是{count}</h2>
            <button onClick={() => dispatch({ type: 'add'})}>Increment</button>
            <button onClick={() => dispatch({ type: 'sub'})}>Decrement</button>
        </div>
    )
}

模拟Redux

  1. 将数据集中于一个store对象
  2. 将所有操作集中于一个reducer中
  3. 创建一个Context
  4. 创建对数据读写的API
  5. 将输入读写的API放入第三步创建的Context中
  6. 用Context.Provider 将Context 提供给所有组件
  7. 组件使用useContext获取数据读写API

3. useContext

  1. 使用C=createContext(inital)创建上下文
  2. 使用<C.provider>规定作用域
  3. 在对应的作用域中使用useContext(C) 使用上下文

useContext 不是响应式

4.useMemo

主要用来解决使用React hooks产生的无用渲染的性能问题,防止函数组件的每一次调用都会执行内部的所有逻辑。

function App () {
    const [n, setN] = useState(0)
    const [m, setM] = useState(0)
    const onClick = () => {
        setN(n + 1);
    };
    const onClick2 = () => {
        setM(m + 1);
    };

    const onClickChild = useMemo(
        () => {
            const fn = div => {
                console.log("on click child, m: " + m);
                console.log(div);
            };
            return fn;
        }, [m]// 如果是函数返回函数可以用useCallback代替
    )
    return (
        <div className="App">
            <div>
                <button onClick={onClick}>update n {n}</button>
                <button onClick={onClick2}>update m {m}</button>
            </div>
            <Child2 data={m} onClick={onClickChild} />
        </div>
    )
}

function Child (props) {
    console.log("child 执行了");
    console.log("假设这里有大量代码");
    return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}

const Child2 = React.memo(Child);
  1. 第一个参数是 ()=> value,回调执行逻辑
  2. 第二个参数是更新的依赖
  3. 只有依赖变化时才重新计算value,执行对应的回调逻辑,参考Vue的计算属性

useCallback

useCallback(x=>log(x),m)

等价于

useMemo(()=> x =>log(x),m)

5.useRef

用于在重新render的过程中保持不变的值

import React, { useRef} from 'react';
function Example8(){
    const inputEl = useRef(null)
    const onButtonClick=()=>{ 
        inputEl.current.value="Hello"
        console.log(inputEl) //输出获取到的DOM节点
    }
    return (
        <>
            {/*保存input的ref到inputEl */}
            <input ref={inputEl} type="text"/>
            <button onClick = {onButtonClick}>在input上展示文字</button>
        </>
    )
}
export default Example8

使用:const count = useRef(0)

读取:count.current,通过引用保证两次的ref是同一个值

使用时不会自动更新UI,这一点Vue不同

forwardRef

实现Ref在props中的传递

import React, { useRef } from "react";

import ReactDOM from "react-dom";


import "./styles.css";

function App() {

    const buttonRef = useRef(null);

    return (

        <div className="App">

            <Button3 ref={buttonRef}>按钮</Button3>

        </div>

);

}


const Button3 = React.forwardRef((props, ref) => {

return <button className="red" ref={ref} {...props} />;

});

const rootElement = document.getElementById("root");

ReactDOM.render(<App />, rootElement);

useImperativeHandle

自定义Ref 用于包装属性

过时闭包

参考文章