react hook使用

什么是Hook?

Hook 是 React 16.8 的新增特性。

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的js函数(就是让你可以用到类组件中的state和生命周期等函数)。

Hook 使你在无需修改组件结构的情况下复用状态逻辑。

Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据) ,而并非强制按照生命周期划分。

使用规则

Hook 不能在 class 组件中使用,只能在函数组件中使用。

Hook必须写在函数的最顶层,不要在循环,条件或嵌套函数中使用。

Hook只在 React 函数中使用,不要在普通的 JavaScript 函数中使用。

useState

如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使用 Hook。
useState 是允许你在 React 函数组件中添加 state 的 Hook。

首先你需要引入
import React, { useState } from 'react';
其次,使用useState方法来添加state,方法的参数,可以是变量、对象或者是函数,这个参数将作为state的初始值,如果是函数,函数的返回值作为初始值。返回一个数组,数组的值为当前state和更新state的函数。

const [count, setCount] = useState(0);声明一个state变量count,并初始化为0(当前状态),也生成了一个让你更新count 变量的方法setCount。

class示例

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>
    );
  }
}

类组件是通过调用this.setSatete()改变state的值。

Hook示例

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量  
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

组件中想要更改state变量count,只能通过useState返回对应的set方法。

useEffect & useLayoutEffect

useEffect

对比类组件的生命周期函数,可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

useEffect,第一个参数是回调函数,第二个参数是依赖项数组。数组的内容会进行上一次和当前的对比,例如在 componentDidUpdate 中添加对 prevPropsprevState 的比较逻辑,在数组内容改变后执行回调函数。
注意: 组件每次渲染会默认执行一次useEffect,如果不传第二个参数,那只要组件有state发生改变,就会触发回调函数;如果参数传一个空数组,那只会在初始化执行一次回调函数。另外,如果return返回了一个函数,那组件在每次重新渲染的时候,都会先执行该函数再调用回调函数。

Effect Hook 可以让你在函数组件中执行副作用操作,数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。
副作用,分为需要清除的和不需要清除的。

无需清除的

比如发送网络请求、手动变更DOM、记录日志等。只需要在useEffect中的方法里写清要处理的逻辑即可,表示每次DOM更新渲染后都会执行useEffect里的方法。

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

需要清除的

比如订阅外部数据源等。

官方文档中的例子,假设我们有一个 ChatAPI 模块,它允许我们订阅好友的在线状态。

class代码

以下是我们如何使用 class 订阅和显示该状态:

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
        isOnline: null 
    };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {    
      ChatAPI.subscribeToFriendStatus(this.props.friend.id,this.handleStatusChange);
  }  
  componentWillUnmount() {    
      ChatAPI.unsubscribeFromFriendStatus(this.props.friend.id,this.handleStatusChange);  
  }  
  handleStatusChange(status) {    
     this.setState({isOnline: status.isOnline });  
  }
  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}
hook代码

由于添加和删除订阅的代码的紧密性,所以 useEffect 的设计是在同一个地方执行。

每个effect 都可以返回一个函数,React 将会在组件卸载时,执行清除操作中调用它,这是effect可选的清除机制。在调用一个新的 effect 之前对前一个 effect 进行清理。

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {    
      function handleStatusChange(status) {      
          setIsOnline(status.isOnline);    
      }    
      ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    
      return function cleanup() {
          ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    
      };  
  });
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

使用 Hook 其中一个目的,就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。因此hook,可以使用多个effect,将不相关的逻辑分离到不同的effect中。同时React将按照effect声明的顺序依次调用组件中的每一个effect。

effect 性能问题

在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevPropsprevState 的比较逻辑解决。在useEffect中,只要传递数组作为他的第二个可选参数即可。

要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

useLayoutEffect

和useEffect用法一致,useEffect是异步调用,而useLayoutEffect是同步调用,所以useEffect的回调函数会在页面渲染后执行;useLayoutEffect会在页面渲染前执行。

两者使用场景

如果回调函数会修改state导致组件重新渲染,可以useLayoutEffect,因为这时候用useEffect可能会造成页面闪烁;
如果回调函数中去请求数据或者js执行时间过长,建议使用useEffect;因为这时候用useLayoutEffect堵塞浏览器渲染。

useMemo & useCallback

都可用于性能优化,减少组件重新渲染。

useMemo

useMemo返回一个memoized,和effect函数一样,将回调函数和依赖项数组作为参数。在依赖项数组发生改变时,重新计算memoized。

注意:
1、传入 useMemo 的函数会在渲染期间执行。不要在这个回调函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
2、 先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。

function MemoDemo() {
    let [count, setCount] = useState(0);
    let [render,setRender] = useState(false)
    const handleAdd = () => {
        setCount(count + 1);
    };
    const Childone = () => {
        console.log("子组件一被重新渲染");
        return <p>子组件一</p>;
    };
    const Childtwo = (props) => {
        return (
            <div>
                <p>子组件二</p>
                <p>count的值为:{props.count}</p>
            </div>
        );
    };
    const handleRender = ()=>{
        setRender(true)
    }
    return (
        <div style={{display:"flex",justifyContent:'center',alignItems:'center',height:'100vh',flexDirection:'column'}}>
            {
                useMemo(() => {
                    return <Childone />
                }, [render])
            }
            <Childtwo count={count} />
            <button onClick={handleAdd}>增加</button>
            <br/>
            <button onClick={handleRender} >子组件一渲染</button>
        </div>
    );
}

Childone组件只有render改变才会重新渲染。

这里顺带讲下,React.memo,用React.memo包裹的组件每次渲染时会和props会和旧的props进行浅比较,如果没有变化则组件不渲染;示例如下

const Childone = React.memo((props) => {
    console.log("子组件一被重新渲染",props);
    return <p>子组件一{props.num}</p>;
})
function MemoDemo() {
    let [count, setCount] = useState(0);
    let [render,setRender] = useState(false)
    let [num,setNum] = useState(2)
    const handleAdd = () => {
        setCount(count + 1);
    };
   
    const Childtwo = (props) => {
        return (
            <div>
                <p>子组件二</p>
                <p>count的值为:{props.count}</p>
            </div>
        );
    };
    const handleRender = ()=>{
        setRender(true)
    }
    return (
        <div style={{display:"flex",justifyContent:'center',alignItems:'center',height:'100vh',flexDirection:'column'}}>
            {/* {
                useMemo(() => {
                    return 
                }, [render])
            } */}
            <Childone num={num}/>
            <Childtwo count={count} />
            <button onClick={handleAdd}>增加</button>
            <br/>
            <button onClick={handleRender} >子组件一渲染</button>
        </div>
    );
}

这个例子是把上个例子中的Childone拆出来套上React.memo的结果,点击增加后该组件不会重复渲染,因为num没有变化。

useCallback

useCallback,父组件传递一个函数给子组件的时候,由于父组件的更新会导致该函数重新生成从而传递给子组件的函数引用发生了变化,这就会导致子组件也会更新,而很多时候子组件的更新是没必要的,所以我们可以通过useCallback来缓存该函数,然后传递给子组件。

还是上面那个例子,我们把handleRender用useCallback包裹,也就是说这里num不变化每次都会传同一个函数,若是这里不用useCallback包裹,每次都会生成新的handleRender,导致React.memo函数中的props浅比较后发现生成了新的函数,触发渲染。

const Childone = React.memo((props) => {
    console.log("子组件一被重新渲染",props);
    return <p>子组件一{props.num}</p>;
})
export default function MemoDemo() {
    let [count, setCount] = useState(0);
    let [render,setRender] = useState(false)
    let [num,setNum] = useState(2)
    const handleAdd = () => {
        setCount(count + 1);
    };
   
    const Childtwo = (props) => {
        return (
            <div>
                <p>子组件二</p>
                <p>count的值为:{props.count}</p>
            </div>
        );
    };
    const handleRender = useCallback(()=>{
        setRender(true)
    },[num])
    return (
        <div style={{display:"flex",justifyContent:'center',alignItems:'center',height:'100vh',flexDirection:'column'}}>
            {/* {
                useMemo(() => {
                    return 
                }, [render])
            } */}
            <Childone num={num} onClick={handleRender}/>
            <Childtwo count={count} />
            <button onClick={handleAdd}>增加</button>
            <br/>
            <button onClick={handleRender} >子组件一渲染</button>
        </div>
    );
}

你可能感兴趣的:(react,hook,javascript,前端,react.js)