[React基础知识]几个use+'...'常用hook

939 阅读4分钟

useRedux践行Flux/Redux思想

使用方法:

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

useContext

全局变量是全局的上下文;上下文是局部的全局变量

使用方法:

  1. 使用 C = createContext(initial) 创建上下文
  2. 使用 <C.Provider> 圈定作用域
  3. 在作用域内使用 useContext(C)来使用上下文

父子孙 需要公用一个地方定义的C = createContext(initial)

test.js文件


import React, { useContext } from "react";

import Home from '@/components/Home';

export const TestContext= React.createContext({});

const Navbar = () => {
  const { username } = useContext(TestContext)

  return (
    <div className="navbar">
      <p>{username}</p>
    </div>
  )
}

const Messages = () => {
  const { username } = useContext(TestContext)

  return (
    <div className="messages">
      <p>1 message for {username}</p>
    </div>
  )
}

export default class testUseContext extends React.Component {
    render() {
        let name ="小人头"
        return (
            <TestContext.Provider value={{	username: 'superawesome'}}>
                 		<div className="test">
                 			<Navbar />
               			  <Messages />
                      <Home />
               </div>
            </TestContext.Provider>
        );
    }
}
import React, { useContext } from "react";
import  { TestContext } from "../pages/device-manage/test";
const Home = () => {
    const { username } = useContext(TestContext)
    return (
      <div className="navbar">
        <p>Home{username}</p>
      </div>
    )
  }

export default Home;  

useEffect

可以直接在此函数中处理生命周期事件,可以看作是componentDidMount、componentDidUpdate、componentDidUnMount这三个生命周期的组合。

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

不传递第二个参数会导致每次渲染都会运行useEffect;

function Demo() {
  useEffect(() => {
    // 默认情况下,每次渲染后都会调用该函数
    console.log('render!');

    // 如果要实现 componentWillUnmount,
    // 在末尾处返回一个函数
    // React 在该函数组件卸载前调用该方法
    // 其命名为 cleanup 是为了表明此函数的目的,
    // 但其实也可以返回一个箭头函数或者给起一个别的名字。
    return function cleanup () {
        console.log('unmounting...');
    }
  })  
  return "hello";
}

effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。这实际上比componentWillUnmount生命周期更强大,因为如果需要的话,它允许咱们在每次渲染之前和之后执行副作用。

不完全的生命周期

useEffect在每次渲染后运行(默认情况下),并且可以选择在再次运行之前自行清理。

与其将useEffect看作一个函数来完成3个独立生命周期的工作,不如将它简单地看作是在渲染之后执行副作用的一种方式,包括在每次渲染之前和卸载之前咱们希望执行的需要清理的东西。

阻止每次重新渲染都会执行 useEffect

如果希望 effect 较少运行,可以提供第二个参数 - 值数组。 将它们视为该effect的依赖关系。 如果其中一个依赖项自上次更改后,effect将再次运行。但如果数组中包含多个,只有一个变化,effect都会执行,但是如果不在数组中包含的值在useEffect函数中使用,此值保持原来的。

const [value, setValue] = useState('5');

useEffect(() => {
  // 仅在 value 更改时更新
  console.log(value);
}, [value]) 

仅在挂载和卸载的时候执行

可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

useEffect(() => {
  console.log('mounted');
  return () => console.log('unmounting...');
}, []) 

这样只会在组件初次渲染的时候打印 mounted,在组件卸载后打印: unmounting。

不过,这隐藏了一个问题:传递空数组容易出现bug。如果咱们添加了依赖项,那么很容易忘记向其中添加项,如果错过了一个依赖项,那么该值将在下一次运行useEffect时失效,并且可能会导致一些奇怪的问题。

只在挂载的时候执行

// 存储对 input 的DOM节点的引用
const inputRef = useRef();

// 将输入值存储在状态中
const [value, setValue] = useState("");

useEffect(
  () => {
    // 这在第一次渲染之后运行
    console.log("render");
    // inputRef.current.focus();
  },
  // effect 依赖  inputRef
  [inputRef]
);

使用 useEffect 获取数据并显示它

const [posts, setPosts] = useState([]);
useEffect(async () => {
	const res = await fetch(
  	"https://www.reddit.com/r/reactjs.json"
	);

	const json = await res.json();

	setPosts(json.data.children.map(c => c.data));
},[setPosts]); // 这里没有传入第二个参数,每次渲染都会执行此函数?

当数据改变时重新获取

const [posts, setPosts] = useState([]);
useEffect(async () => {
  const res = await fetch(
    `https://www.test.com/r/${test}.json`
  );

  const json = await res.json();
  setPosts(json.data.children.map(c => c.data));

  // 当`test`改变时重新运行useEffect:
}, [test, setPosts]);

可以对比Vue的watch 和 小程序的 behaivor

useLayoutEffect

已经形成真实DOM之后执行,但是是render之前

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

useLayoutEffect 在浏览器渲染前执行

useLayoutEffect 总比 useEffect 先执行

useLayoutEffect 里的任务最好影响了 Layout

为了用户体验,优先使用 useEffect (优先渲染)

用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.

useState(initial)返回一个数组,其中第一项是状态值,第二项是一个更新状态的函数。

  • 要确保对useState()的多次调用在渲染之间始终保持相同的顺序

  • 函数组件有状态,在组件的函数体中调用useState()

  • 在单个组件中可以有多个状态:调用多次useState()

  • 当初始状态开销很大时,延迟初始化很方便。使用计算初始状态的回调调用useState(computeInitialState),并且此回调仅在初始渲染时执行一次

  • 必须确保使用useState()遵循 Hook 规则

  • 当闭包捕获过时的状态变量时,就会出现过时状态的问题。可以通过使用一个回调来更新状态来解决这个问题,这个回调会根据先前的状态来计算新的状态。

  • 可以使用useState()来管理一个简单的状态。为了处理更复杂的状态,使用useReducer() hook。

const [state, setState] = useState(initialState);

// 将状态更改为 'newState' 并触发重新渲染
setState(newState);

// 重新渲染`state`后的值为`newState`

调用规则:

  • 仅顶层调用 Hook :不能在循环,条件,嵌套函数等中调用useState()。在多个useState()调用中,渲染之间的调用顺序必须相同。

  • 仅从React 函数调用 Hook:必须仅在函数组件或自定义钩子内部调用useState()。 使用 setState(newState)来更新状态值。另外,如果需要根据先前的状态更新状态,可以使用回调函数setState(prevState => newState)。

useRef存储对DOM节点的引用,它还相当于 this , 可以存放任何变量

可以用来引用 DOM 对象,也可以用来引用普通对象

useRef类似于React.createRef。

createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。

useRef可以很好的解决闭包带来的不方便性.可以在各种库中看到它的身影, 比如 react-use 中的 useInterval , usePrevious 。。。

值得注意的是,当 useRef 的内容发生变化时,它不会通知您。更改.current属性不会导致重新呈现。因为他一直是一个引用 .

function App() {
  const refInput = React.useRef(null);
  React.useEffect(() => {
    refInput.current && refInput.current.focus();
  }, []);

  return <input ref={refInput} />;
}

应用场景:父组件调用子组件方法

function Child(props, ref) {
  const [text, setText] = useState("");
  // 该 hook 需要定义抛出给父组件的可以使用的 api 方法
  // 相当于代理了子组件的方法
  useImperativeHandle(ref, () => ({
    setTextByParent(text = "") {
      setText(text);
    },
  }));
  return <p>text: {text}</p>;
}

// 函数组件需要使用 forwardRef 包裹
const ForwardChild = forwardRef(Child);

function Parent() {
  const ref = useRef(null);

  return (
    <>
      <ForwardChild ref={ref} />
      <button
        onClick={() => {
          ref.current &&
            ref.current.setTextByParent("text updated by parent component");
        }}
      >
        set text by parent
      </button>
    </>
  );
}

useRef保存任何可变得值,类似于在 class 中使用实例字段的方式。它创建的是一个普通js对象

useRef() 和自建一个 {current: ...} 对象的唯一区别是:useRef 会在每次渲染时返回同一个 ref 对象

forwardRef(props 无法传递 ref 属性)

实现 ref 的传递:由于 props 不包含 ref,所以需要 forwardRef

import React, {forwardRef, useRef} from 'react';

function App(){
    const buttonRef = useRef(null)
    return (
        <div>
            <Button ref={buttonRef}>按钮</Button2>
        </div>
    )
}
const Button = forwardRef((props, ref) => {
    console.log(ref)  //可以拿到ref对button的引用,forwardRef仅限于函数组件,class 组件是默认可以使用 ref 的
    return <button ref={ref} {...props} />;
})

//const Button = (props) => {
  //  console.log(props) // {ref: undefined, children: "按钮"}
 //   return <button {...props} />
//}

useImperativeHandle

正常情况下 ref 是不能挂在到函数组件上的,因为函数组件没有实例,但是 useImperativeHandle 为我们提供了一个类似实例的东西。 它帮助我们通过 useImperativeHandle 的第 2 个参数,所返回的对象的内容挂载到 父组件的 ref.current 上。

forwardRef会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。

import React, { forwardRef, useImperativeHandle, useEffect, useRef } from 'react'

const TestRef = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
  // 挂载到父组件ref.current上
    open() {
      console.log("open")  //打印出open
    }
  }))
})

function App () {
  const ref = useRef()
  useEffect(() => {
    ref.current.open()  //获取到父函数
  },[])
  
  return(
    <>
      <div>石小阳</div>
      <TestRef ref={ref}></TestRef>
    </>
  )
}
export default App

ref

普通的类组件有实例所以可以用过 React.createRef() 挂载到节点或组件上,然后通过 this 获取到该节点或组件。

class RefTest extends React.Component{
    constructor(props){
        super(props);
        this.myRef=React.createRef();
    }
    componentDidMount(){
        console.log(this.myRef.current);
    }
    render(){
        return <input ref={this.myRef}/>
    }
}

useMemo表示对函数求值进行缓存,当依赖没有发生变化时,不执行计算,直接返回缓存结果

引入于16.6.0

有点像 vue 的计算属性,对于一些复杂耗时的计算,使用它能够优化程序的性能。

第一个参数是 () => value
第二个参数是依赖 [m, n]
只有当依赖变化时,才会计算出新的 value
如果依赖不变,那么就重用之前的
这不就是 Vue 2的 computed 吗?

如果你的 value 是一个函数,那么你就要写成useMemo(() => x => console.log(x)),这是一个返回函数的函数,是不是很难用?于是就有了useCallback

使用方式与useCallback类似,只不过useMemo返回的是一个值。

const newValue = useMemo(() => Math.sqrt(value), [value]);

useMemo() 是在 render 期间执行的,所以不能进行一些额外的副操作,比如网络请求等。

useCallback是返回一个memoized函数,用来对函数进行缓存,防止总是重复的生成新的函数。

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

useCallback(x => console.log(x), [m]) 等价于

useMemo( () => x => console.log(x), [m])

可以通过把函数add进Set中,来获取set.size,来监控函数是否被缓存

export function Demo() {
  const [count, setCount] = useState(0);
/*
//count值始终为1
  const changeCount = () => {
    setCount(count + 1);
  }; 
  
这是因为useCallback的第二个参数没有指定,它只会在第一次时进行changeValue闭包缓存,所以初始是0,缓存后就一直为0,执行一次自增,所以值始终是1。
*/
//指定useCallback的依赖,当依赖发生变化时,其自动更新缓存
  const changeValue = useCallback(
    () => {
      setValue(value + 1);
    },
    [value]
  );

  return (
    <div>
      {/*ComplexComponent是一个十分复杂的组件*/}
      <ComplexComponent onIncrement={changeCount} />
      <button onClick={changeValue}>count</button>
    </div>
  );
}

使用场景:通常在将一个组件中的函数,传递给子元素进行回调使用时,使用useCallback对函数进行处理

useRequest

const { data: monitorPageConfList, loading } = useRequest(
    async () => {
        return monitorApi.getMonitorPageConf();
    },
    {
        refreshDeps: [],
        onError: (error) => {
            message.error(`获取监控配置失败:${error.message}`);
        }
    },
);
const { data: aAndbList } = useRequest(
  async () => {
    const aList = await getAList(branchType, Number(branchId));
    let imageQuality = null;
    if (instance) {
      bList = await getBlist(instance.id);
    }
    return { aList, bList };
  },
  {
    refreshDeps: [branchId],
  },
);

www.jianshu.com/p/bf9f66ac3…

blog.csdn.net/gg_18826075…

segmentfault.com/a/119000002…