react-hooks 在不编写 class 的情况下使用 state 以及其他的 React 特性

hooks

一、hook

1.useState()

​ 1). State Hook 让函数组件也可以有 state 状态,并进行状态数据的读写操作

​ 2). 语法 const [fruit, setFruit] = useState(123)

​ 3). useState() 说明:

​ 参数:第一次初始化指定的值在内部作缓存

​ 返回值:包含2个元素的数组(xxx, setxxx),第一个为内部当前状态值,第2个为更新状态值的函数

  	var ageState = useState(123); // 返回一个有两个元素的数组
  	var age = ageState[0]; // 数组里的第一个值
  	var setAge = ageState[1]; // 数组里的第二个值

​ 4). setState() 2中写法

​ setState(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值

​ setState(val => newVal):参数为函数,接收原来的状态值,返回新的状态值,内部用其覆盖原来的状态值

// 一个组件中可以声明多个 state 变量!
  const [xxx, setxxx] = useState(1);
  const [xxxx, setxxxx] = useState('222');
  const [xxxxx, setxxxxx] = useState([{
    
    name:'zhangning'}]);

2.useEffect()

​ 1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的声明周期钩子)

​ 默认情况下,它在第一次渲染之后每次更新之后都会执行

​ React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

​ 2). React 中的副作用操作:

​ 发 ajax 请求数据获取、设置订阅 / 启动定时器、手动更改真实 DOM

​ 3). effect 将在每轮渲染结束后执行,但你可以选择让它在只有某些之改变的时候才执行。

	useEffect(()=>{
    
    })
	useEffect(()=>{
    
    }, [])

	useEffect(()=>{
    
    }, [xxx])

	useEffect(()=>{
    
    
        // effect 可选的清楚机制(订阅或计时器ID等),为防止内存泄漏,清楚函数会在组件卸载前执行
        return ()=>{
    
    }
    }, [xxx])

3.useLayoutEffect()

​ 1.与useEffect相同,他会在所有的DOM变更之后同步调用effect

​ 2.可以使用它来读取DOM布局并同步触发重渲染。在浏览器绘制之前,useLayoutEffect内部的更新计划被同步刷新

4.自定义Hook

​ 1).自定义Hook是一个函数,必须以use开头,函数内部可以调用其他Hook

// 监听浏览器窗口大小
export function useWinSize() {
    
    
  const [size, setSize] = useState({
    
    
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });

  const onResize = useCallback(() => {
    
    
    setSize({
    
    
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    });
  }, [size.width]);

  useEffect(() => {
    
    
    window.addEventListener('resize', onResize);
    // return 表示只有销毁的时候才执行
    return () => {
    
    
      window.removeEventListener('resize', onResize);
    };
  }, []);

  return size;
}
const width = useWinSize()

5.useRef()

​ 1).Ref Hook 可以在函数组件中存储、查找组件内的标签或任意其他数据

​ 2).语法:const refCont = useRef()

​ 3).作用:保存标签对象,功能与React.CreateRef()一样

import React, {
    
    useRef} from 'react';
import Children from './components/Children';

function Index() {
    
    
  const chRef = useRef();
  // 通过 current 访问子组件
  console.log(chRef.current)
  return (
    <>
      <Children ref={
    
    chRef}/>
    </>
  );
}
export default Index;

6.useImperativeHandle()

​ 1.可以在使用ref时自定义暴露给父组件的实例值。

useImperativeHandle(ref, createHandle, [deps])

​ 2.与forwardRef一起使用

// 父组件
function Parent() {
    
    
  const childRef = useRef();
  return (
    <div>
      <AllProjectTable ref={
    
    childRef}/>
      <button onClick={
    
    () => childRef.current.handleClick()}>aaaa</button>
    </div>
  );
}

// 子组件
const Child=(props, ref)=> {
    
    
    const [state, setState] = useSataeO();
  const handleClick = () => {
    
    };
  // 暴露给父组件的方法
  useImperativeHandle(ref, () => ({
    
    
    handleClick
  }));
  return (
    <>
      <button onClick={
    
    () => handleClick()}>dddd</button>
    </>
  );
}
// 子组件通过 forward 包裹起来
export default forwardRef(Child);

7.useContext()

​ 理解:一种组件间通信方式,常用于 祖组件 与 后代 组件间通信

​ 1.接收context对象并返回context的当前值,当前context值由上层组件中距离当前组件最近的<MyContext.Provider>的属性值prop决定

​ 2.即使祖先使用 React.memo 或shouldComponentUpdate,也会在组件本身使用 useContext时重新渲染。

​ 3.调用useContext的组件总会在context值变化时重新渲染

使用方式
1. 创建 Context 容器对象:
    const MyContext = React.createContext()
2.渲染子组件时,外面包裹 MyContext.Provider 通过 value 属性给后代组件传递数据
    <MyContext.Provider value={数据}>子组件</MyContext.Provider>
3.后代组件读取数据:
    // 第一种:仅适用于类组件,了解下就行
    const contextType = MyContext // 声明接收 context
    this.context // 读取 context 中的 value 数据
    // 第二种:函数组件、类组件都可以使用
    <MyContext.Consumer>
        {
            value => {// value 就是 context 中的 value 数据
                显示内容            
            }
        }
    </MyContext.Consumer>

​ 示例

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

// 创建 Context 容器对象
// 渲染子组件时,外面包裹 MyContext.Provider,通过 value 属性给后代组件传递数据
const MyContext = createContext({});
const {Provider, Consumer} = MyContext;

function AAA() {
  const name = 'zhangning';
  const age = 24;
  return (
    <div>
      我是祖组件,name:{name}
      <Provider value={
   
   {name, age}}>
        <BBB/>
      </Provider>
    </div>
  );
}
function BBB() {
  return <div><CCC/></div>;
}
function CCC() {
  // 这里也可以使用 useContext 直接获取数据,个人感觉更加方便实用
  // const {name} = useContext(MyContext);
  return <div>
    从祖组件获取的name:
    <Consumer>
      {value => {return `${value.name},年龄${value.age}`;}}
    </Consumer>
  </div>;
}

export default AAA;

8.useReducer()

​ 1.相当于useState的代替方案,接收一个(state, action)=>newState的reducer,并返回当前的state以及与其配套的dispatch方法。

​ 2.在某些场景下,useReducer会更加适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用useReducer还能给那些会触发深更新的组件做性能优化,可以向子组件传递dispatch而不是回调函数

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

​ 官网并不推荐使用reducer来模拟Redux…

9.useMemo()

​ 1.返回一个memoized缓存值

​ 把创建函数和依赖项数组作为参数传入useMemo,只会在依赖项改变时才重新计算memoized缓存值。避免在每次渲染时都进行高开销的计算。

​ 传入useMemo的函数会在渲染期间执行。没有依赖项,每次渲染时都会计算新的值

​ 可以通过useMemo作为性能优化的方案。

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

10.useCallback()

​ 1.把回调函数及依赖项数组作为参数传入,返回该回调函数的memoized缓存版本,该回调函数在依赖项改变时才会更新。

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

​ 概念上说,所有回调函数中引用的值都应该出现在依赖项数组中。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

二、组件优化

1.component

​ 先了解一下在react中的class类组件中的性能优化方面,主要集中于一下两点

​ 1.调用setState时,就会触发组件重新渲染,无论前后state是否改变

​ 2.父组件更新,子组件也会自动更新(效率较低)

​ 提高效率的方法:

​ 让组件在 state 或 props 数据发生改变时 再进行 render()

​ 原因:只要调用了 setState 方法,组件中的 shouldComponentUpdate() 总是返回 true,导致组件 render

​ 解决方案

// 方案一:通过重写 shouldComponentUpdate()方法,比较新旧 state 或 props,有变化返回 true,没有变化返回 false
shouldComponentUpdate(nextProps, nextState) {
  console.log(this.props, this.state);// 当前 props 和 state
  console.log(nextProps, nextState);// 改变之后的 props 和 state
  if (JSON.stringify(nextProps) === JSON.stringify(this.props)
    || JSON.stringify(nextState) === JSON.stringify(this.state)) {
    return false;
  } else {
    return true;
  }
}
// 方案二: 使用 PureComponent 
// PureComponent 重写了 sholdComponentUpdate(),只有 state 或 props 数据有变化才返回 true
// 注意:只是进行 state 和 props 数据的浅比较,如果只是数据对象内部数据变了,返回 false、
// 不要直接修改 state 数据,而是要产生新数据
// 项目中一般使用 pureComponent 来优化
// pureComponent 缺点:可能会因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate 的结果返回 false,界面得不到更新

2.hooks中优化性能方案

​ 在函数式组件中失去了 shouldComponentUpdate ,我发通过判断前后状态来决定是否更新。在函数式组件中,react 不再区分 mount 和 update 两个状态,也就是说函数组件的每一次调用都会执行其内部的所有略记,会带来较大的性能损耗。在此,hooks 中出现了两个钩子 useMemo 和 useCallback 来解决函数式组件的性能方案。

​ 先看下 useMemo 和 useCallback 的源码

function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;

​ 参数形式和 useEffect 一致,useEffect 作用是用于处理副作用,这两个则不行。

​ useMemo 和 useCallback 都会在组件第一次渲染的时候执行,之后会在依赖改变时再次执行;

​ 都返回缓存的值,useMemo 返回缓存的变量,useCallback 返回缓存的函数。

优点:useMemo包裹的变量,相当于对变量做了缓存,当父组件重新渲染时,变量不会改变==》子组件不会重新渲染

// useMemo 示例
function Test() {
  const [count, setCount] = useState(1);
  const [val, setVal] = useState('');

  // sum1 每次在 count 与 val 有一个改变的时候都会刷新组件重新执行 sum1。
  // 但是这里的计算 sum1 只依赖于 count 的值,在 val 修改的时候没有必要进行 sum1 的计算。
  // 这种情况下,我们就可以使用 useMemo,只在count 的值修改时执行计算(sum2)
  const sum1 = () => {
    console.log('sum1111');
    let sum = 0;
    for (let i = 0; i < count; i++) {
      sum += i;
    }
    return sum;
  };

  // 每次在 count 的值改变的时候才重新执行计算
  const sum2 = useMemo(() => {
    console.log('sum2222');
    let sum = 0;
    for (let i = 0; i < count; i++) {
      sum += i;
    }
    return sum;
  }, [count]);

  return (
    <div>
      <h1>{count}+++{val}+++{sum1()}+++{sum2()}</h1>
      <div>
        <button onClick={() => setCount(count + 1)}>+++</button>
        <input type='text' value={val} onChange={event => setVal(event.target.value)}/>
      </div>
    </div>
  );
}

useCallback

​ 为什么使用 useCallback,以及 useCallback 所能带来的性能提升

​ 和 useMemo 类似,当回调函数床底给经过优化的并使用引用相等性去避免非必要渲染的子组件时,他非常有用,和 pureComponent 的作用相同。就是说父组件传递一个函数给子组件的时候,由于父组件的更新会导致该函数重新生成从而传递给子组件的函数引用发生了变化,这就会导致子组件也会更新, 很多情况下子组件的更新并不是必要的,所以通过 useCallback 来缓存传递给子组件的函数。

优点:useCallback包裹的函数,相当于对函数做了缓存,当父组件重新渲染时,函数不会重新定义==》子组件不会重新渲染

const Child = ({getNum}) => {
  return <h1>数据:{getNum}</h1>;
};

function Test() {
  const [count, setCount] = useState(1);
  const [val, setVal] = useState('');

  const sum = useCallback(() => {
    return Array.from({length: count}, (v, i) => i).reduce((a, b) => a + b);
  }, [count]);

  return (
    <div>
      <Child getNum={sum}/>
      <h1>{count}+++{val}++++{sum()}</h1>
      <div>
        <button onClick={() => setCount(count + 1)}>+++</button>
        <input type='text' value={val} onChange={event => setVal(event.target.value)}/>
      </div>
    </div>
  );
}
// 以上只有当 count 改变的时候child组件才会重新渲染

总结:

memo用于包裹子组件

​ memo针对一个组件的渲染是否重复执行

	<Header />

​ usememo 针对一段函数逻辑是否重复执行

	()=>{}

​ useEffect 是在渲染之后完成的

​ useMemo是在渲染期间完成的(空数组只会执行一次)

	useMemo(()=>{},[])

​ useCallback

	useMemo(()=>{fn})等价于useCallback(fn)

3.Memo

​ memo() 和 PureComponent 相似,它帮助我们控制何时重新渲染组件

​ 理解就是,memo帮我们缓存组件,通过第二个参数的条件判断是否重新渲染

​ 示例:

​ 1.不适用memo,父组件更新,子组件也会更新

​ 2.父组件更新,子组件memo不适用第二个参数,默认对比 pre 和 next 里的每一项内容,父组件不传递任何属性给子组件,父组件更新属性,子组件不刷新

import React, {memo} from 'react';
import './index.scss';

const Header = memo(props => {
  return (
    <div>
      header 组件
    </div>
  );
});

export default Header;

​ 3.使用memo第二个参数,对比父组件传递给子组件的所有属性,有变化则更新

import React, {memo} from 'react';
import './index.scss';

const Header = memo(props => {
  return (
    <div>
      header 组件
    </div>
  );
}, (pre, next) => {
  const keys = Reflect.ownKeys(next);
  return keys.every(i => pre[i] === next[i]);
});

export default Header;

​ 4.使用memo第二个参数,对比某一个属性,对比父组件传递给子组件的所有属性,有变化则更新

import React, {memo} from 'react';
import './index.scss';

const Header = memo(props => {
  return (
    <div>
      header 组件
    </div>
  );
}, (pre, next) => {
  return pre.val === next.val;
});

export default Header;

三、

如何像组件内部动态传入带内容的结构(标签)?

​ Vue 中:使用 slot 技术,也就是通过组件标签体传入结构

​ React 中:

​ 使用 children props:通过组件标签体传入结构,下面的示例1

​ 使用 render props:通过组件标签属性传入结构,一般用 render 函数属性,下面示例 2

​ 使用children props

// children props 示例1
import React from 'react';

function AAA() {
  return (
    <><BBB>hello!</BBB></>
  );
}

function BBB(props) {
  return <>
    <h2>我是BBB组件</h2>
    {/*这里可以打印 AAA 父组件传递过来的 hello!,通过 props.children 接收*/}
    {props.children}
  </>;
}

export default AAA;

​ 使用render props

    // A 和 B 组件父子组件
    <A render={(data)=><B data={data}></B>}></A>
    A 组件:{this.props.render(内部 state 数据)}
    B 组件:读取 A 组件传入的数据显示 {this.props.data}
// 示例2
import React from 'react';

function AAA() {
  return (
    <>
      <BBB render={(name) => <CCC name={name}/>}></BBB>
      {/*<BBB peiqi={(name) => <CCC name={name}/>}></BBB>*/}
    </>
  );
}

function BBB(props) {
  const name = 'zhangning';
  return <>
    <h2>我是BBB组件</h2>
    {/*调用 render 方法,预留一个位子,放置组件,传递 name*/}
    {/*在 A 组件中,使用 B 组件,调用 render 方法,加载 B 组件的子组件 C,传递name,相当于 vue 中的插槽slot*/}
    {/*这里 render 可以写任意字符,在调用的时候保持一致就可,render 便于阅读*/}
    {/*{props.peiqi(name)}*/}{/*A 组件中使用的时候也要使用 peiqi*/}
    {props.render(name)}
  </>;
}

function CCC(props) {
  return <>
    <h2>我是BBB组件</h2>
    接收到B组件中的数据name:{props.name}
  </>;
}

export default AAA;

四、组件通信方式

​ 1.组件之间的关系

​ 1.父子组件

​ 2.兄弟组件(非嵌套组件)

​ 3.祖孙组件(跨级组件)

几种通信方式:
    1.props:
    	children props
    	render props
    2.消息订阅-发布
    	pubs-sub、event 等等
    3.集中式管理:
    	redux、dva
    4.context
    	生产者-消费者模式
比较好的搭配方式:
    父子组件:props
    兄弟组件:消息订阅-发布、集中式管理
    祖孙组件:消息订阅-发布、集中式管理、context(开发用的少,封装插件用的多)

猜你喜欢

转载自blog.csdn.net/qq_37440870/article/details/126614055