react hooks

566

UseContext——跨组件层级穿值

context即上下文

js中有个执行上下文一样,高所组件当下的环境是什么.

createContext创建的ObjectContextParentSon中都需要使用到

const ObjectContext = createContext()

provider(父组件)中的使用方法是

<ObjectContext.Provider value={initValue}>
	<Son />
<ObjectContext.provider />

consumer(子组件)中使用的方法是

function Son() {
    const count = useContext(ObjectContext)
    <div>count<div />
}

完整代码

import "./styles.css";

import React, { useState, createContext, useContext } from "react";
const CountContext = createContext();
function Son() {
  const count = useContext(CountContext); // 一句话就可以得到count
  console.log("useContext(createContext())的返回值", count);
  return <h2 style={{ border: "1px solid red" }}>子组件{count}</h2>;
}
function Parent() {
  const [count, setCount] = useState(0);
  console.log("createContext()的返回值", CountContext);
  console.log("useContext(createContext())的返回值", count);
  return (
    <div style={{ border: "1px solid black" }}>
      <p>You clicked {count} times</p>
      <button
        onClick={() => {
          console.log("父组件函数触发,状态修改");
          setCount(count + 1);
        }}
      >
        click me
      </button>
      <CountContext.Provider value={count}>
        <div>
          <Son />
        </div>
      </CountContext.Provider>
    </div>
  );
}
export default Parent;
// index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  rootElement
);

UseReducer:集中状态管理

useReducer接受一个处理函数和初始值为参数,返回一个实时值和一个修改值的dispatch方法


function ReducerDemo() {
  const [count, dispatch] = useReducer((state, action) => {
    switch (action) {
      case 'add':
        return state + 1;
      case 'sub':
        return state - 1;
      default:
        return state;
    }
  }, 0);
  return (
    <div>
      <h2>现在的分数是{count}</h2>
      <button onClick={() => dispatch('add')}>Increment</button>
      <button onClick={() => dispatch('sub')}>Decrement</button>
    </div>
  );
}

export default ReducerDemo;

用useReducer和useContext实现redux功能:

根组件的结构如下,一个context.provider包裹两个consumer

// example6/Example6.js
import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';
import { Color } from './color'; // 引入Color组件

function Example6() {
  return (
    <div>
      <Color>
        <ShowArea />
        <Buttons />
      </Color>
    </div>
  );
}

export default Example6;

provider中内容如下:通过useContext的provider传递useReducer返回的实时值和修改方法

import React, { createContext, useReducer } from 'react';

export const ColorContext = createContext({});

export const UPDATE_COLOR = 'UPDATE_COLOR';

const reducer = (state, action) => {
  switch (action.type) {
    case UPDATE_COLOR:
      return action.color;
    default:
      return state;
  }
};

export const Color = props => {
  const [color, dispatch] = useReducer(reducer, 'blue');
  return <ColorContext.Provider value={{ color, dispatch }}>{props.children}</ColorContext.Provider>;
};

接受状态的组件showArea:

// example6/ShowArea.js
import React, { useContext } from 'react';
import { ColorContext } from './color';

function ShowArea() {
  const { color } = useContext(ColorContext);
  return <div style={{ color }}>字体颜色为{color}</div>;
}

export default ShowArea;

接受修改状态的Button:

// example6/Button.js
import React, { useContext } from 'react';
import { ColorContext, UPDATE_COLOR } from './color';

function Buttons() {
  const { dispatch } = useContext(ColorContext);
  return (
    <div>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: 'red' });
        }}
      >
        红色
      </button>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: 'yellow' });
        }}
      >
        黄色
      </button>
    </div>
  );
}

export default Buttons;

渲染:

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Example from './example6/Example6';

ReactDOM.render(<Example />, document.getElementById('root'));

useMemo ——避免当前组件更新时不必要的大量计算

当一个组件的属性或者状态改变时,会触发重新渲染.

函数内的变量也会再创建一次.如果一个变量是大量计算得来的,那么这个计算过程也会执行一次.

这就是性能的损耗

import React, { useState, useMemo } from 'react';

function Example7() {
  const [A, setA] = useState('A属性');
  const [B, setB] = useState('B属性');
  const [C, setC] = useState('C属性');
  return (
    <div style={{ border: '1px solid black' }}>
      <div>父组件</div>
      <button
        style={{ border: '1px solid black', borderRadius: 5 }}
        onClick={() => {
          setA(`A属性,${new Date().getTime()}`);
        }}>
        改变属性A
      </button>
      <button
        style={{ border: '1px solid black', borderRadius: 5 }}
        onClick={() => {
          setB(`B属性,${new Date().getTime()}`);
        }}>
        改变属性B
      </button>
      <ChildComponent a={A} b={B} c={C} />
    </div>
  );
}
function ChildComponent({ a, b, c }: { a: any; b: any; c: any }) {
  function process(name: any) {
    console.log(`${name} is processing`);
    return `----${name}---`;
  }

  const aResult = useMemo(() => process(a), [a]);
  const bResult = useMemo(() => process(b), [b]);
  // c没有被缓存,导致A、B属性变化时,process(c)也会执行
  const cResult = process(c);
  return (
    <div style={{ border: '1px solid red' }}>
      <div>{aResult}</div>
      <div>{bResult}</div>
      <div>{cResult}</div>
    </div>
  );
}
export default Example7;

将需要繁重计算的方法用useMemo包裹,并添加上它的依赖项

可以避免组件更新时函数没有必要的执行

 const aResult = useMemo(() => process(a), [a]);

原来是

const cResult = process(c);

useMemo和useEffect的执行先后顺序问题

useCallback ——避免父组件更新时,子组件没有必要的渲染

useCallback和memo连用,达到类组件中pureComponent的效果,但是使用时要注意如何使其真正达到性能优化。

在这个案例中,父组件内维护了一个状态,父组件传递一个函数C给子组件。
通过这个函数C,可以修改父组件的状态,状态的修改带来组件的更新,包括C的更新。

  • 在没有性能优化/性能优化无效的情况下,触发函数C,处理父组件的重新渲染,还包括子组件的重新渲染,
  • 在有效的性能优化的情况下,触发函数C,只会导致父组件的重新渲染
  1. 没有做任何性能优化
import React, { memo, useState, useCallback } from 'react';

const ParentComponent = () => {
  console.log('--------parent rendering--------');
  const [count, setCount] = useState(0);
  const C = () => {
    console.log('clicked ChildrenComponent');
    setCount(preCount => preCount + 1);
  };

  const F = () => {
    console.log('clicked ParentComponent');
    setCount(preCount => preCount + 1);
  };

  return (
    <div>
      <button onClick={F}>ParentComponent </button>
      <div>count ={count}</div>
      <ChildrenComponent C={C} />
    </div>
  );
};

const ChildrenComponent = ({ C }) => {
  console.log('--------ChildrenComponent rending--------------');
  return <button onClick={C}>ChildrenComponent 触发C</button>;
};
export default ParentComponent;

image.png

无论点击子组件还是父组件,都会导致两次渲染。

  1. usecallback+memo性能优化
import React, { memo, useState, useCallback } from 'react';

const ParentComponent = () => {
  console.log('--------parent rendering--------');
  const [count, setCount] = useState(0);
  const C = () => {
    console.log('clicked ChildrenComponent');
    setCount(preCount => preCount + 1);
  };
  // 性能优化1:将传递给子组件的函数用useCallback包裹
  const callbackC = useCallback(() => {
    C();
  }, []);

  const F = () => {
    console.log('clicked ParentComponent');
    setCount(preCount => preCount + 1);
  };

  return (
    <div>
      <button onClick={F}>ParentComponent </button>
      <div>count ={count}</div>
      <ChildrenComponent C={callbackC} />
    </div>
  );
};
// 性能优化2:将自组件用memo包裹
const ChildrenComponent = memo(({ C }) => {
  console.log('--------ChildrenComponent rending--------------');
  return <button onClick={C}>ChildrenComponent 触发C</button>;
});
export default ParentComponent;

image.png

无论点击子元素还是父组件,都只会导致父组件的渲染。

  1. pureComponent
import React, { Component, Fragment, PureComponent } from 'react';

// 性能优化:子组件继承PureComponent
class ChildClass extends PureComponent {
  render() {
    console.log('-----childComponent Rendering------');
    const { C } = this.props;
    return (
      <div>
        <button onClick={C}>ChildrComponent 触发C</button>
      </div>
    );
  }
}

export default class ParentComponent extends Component {
  state = {
    count: 1,
  };

  F = () => {
    console.log('click parentComponent');
    this.setState({ count: this.state.count + 1 });
  };

  C = () => {
    console.log('click childComponent');
    this.setState({ cnt: this.state.count + 1 });
  };

  render() {
    console.log('-----parentComponent Rendering------');
    return (
      <Fragment>
        <div>
          <button onClick={this.F}>ParentComponent</button>
          <ChildClass C={this.C} />
        </div>
      </Fragment>
    );
  }
}

image.png

无论点击子元素还是父组件,都只会导致父组件的渲染。

useRef——保存节点或者当前的值

useRef可以用来对应一个dom节点或者是一个实时值 对应dom节点时使用方法如下

  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.value = 'Hello ,useRef';
    console.log('inputRef', inputEl);
  };
  <input ref={inputEl} type="text" />

对应实时值的使用方法如下

  const [text, setText] = useState('jspang');
  const textRef = useRef();

  useEffect(() => {
    textRef.current = text;
    console.log('textRef:', textRef);
  });

完整代码

import React, { useRef, useState, useEffect } from 'react';
function Example8() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.value = 'Hello ,useRef';
    console.log('inputRef', inputEl);
  };
  // -----------关键代码--------start
  const [text, setText] = useState('jspang');
  const textRef = useRef();

  useEffect(() => {
    textRef.current = text;
    console.log('textRef:', textRef);
  });
  // ----------关键代码--------------end
  return (
    <>
      {/* 保存input的ref到inputEl */}
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>在input上展示文字</button>
      <br />
      <br />
      <input
        value={text}
        onChange={e => {
          setText(e.target.value);
        }}
      />
    </>
  );
}

export default Example8;

useImperativeHandle——父组件调用子组件方法

// 子组件定义
function FancyButton(props: ButtonProps, ref) {
  const testFunction = () => {
    console.log(123);
  };
  useImperativeHandle(ref, () => ({
    test: () => {
      testFunction();
    },
  }));
  return (
    <button ref={ref} className="FancyButton">
      {props.children}
    </button>
  );
}
// 一定要配置forwardRef使用,来转发ref
const FancyButtonRef = React.forwardRef<unknown, ButtonProps>(FancyButton);

// 父组件调用
const ref = React.useRef();
<FancyButtonRef ref={ref}>Click me!</FancyButtonRef>;
ref.current.test();

useState

useState 有状态时使用上一次保存的状态,没有状态时使用默认数值,有状态的组件是用户在填写的表单,是一个能记住上一次浏览位置的长列表,它需要一个位置来表征它的状态,