react---hooks

274 阅读6分钟

1.react的hooks是干什么的

说白了,就是拿function当组件,因为之间用react定义组件用的class关键字,人家嫌麻烦,代码量太大,就折腾了这货,直接一个function就是一个组件。

那么,原来用class定义的组件里可以写state,有生命周期,这货一个function怎么实现那些功能呢?

下面就来介绍一下怎么在function里处理state和生命周期的。

2.useState

hook的useState就是class里面的state,从它里面可以声明组件里用到的state,举个例子

class写法

class Example extends react.Component {
    constructor(props){
        this.state = {
            count: 0,
        }
    }
    render() {
        return (
            <div>{this.state.count}</div>
        )
    }
}

hook写法

function Example(){
    const {count, setCount} = useState(0);
    return <div>{count}</div>
}

简单吧,声明一个state,就要附带一个setXXX,这就相当于setState(),比如这里声明的是count,那么想更新值就要声明一个setCount变量(他也是一个方法),通过setCount(1)更新count的值后,组件就会重新渲染

useState()方法中的参数是默认值的意思,上面count我想让它是一个init类型的值,就给了默认值为0,这次声明了个data,我想让他是一个数组,就给了个[]

更新state,如下例子

function Example() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>clicked {count}</button>
    </div>
  );
}

当然如果不想直接在onClick事件里调用 setCount 方法,想自定义方法来更新count的值,也可以在这个function组件里再定义一个方法来处理

function Example() {
  const [count, setCount] = useState(0);

  const updateCount = function() {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={() => updateCount()}>clicked {count}</button>
    </div>
  );
}

怎么在useState里声明多个变量呢?

function Example() {
  const [state, setState] = useState({
    count: 0,
    data: [1, 2, 3]
  });

  const updateCount = function() {
    setState({ ...state, count: state.count + 1 });
  };

  return (
    <div>
      <ul>
        {state.data.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button onClick={() => updateCount()}>clicked {state.count}</button>
    </div>
  );
}

3.useReducer

除了从 useState 里声明state外,还可以用另一个hook useReducer ,用法类似于 redux 里的 reducer

function Example() {
  const [count, dispatch] = useReducer((count, { type, action }) => {
    switch (type) {
      case "update":
        return action.payload;
      default:
        break;
    }
  }, 0);

  return (
    <div>
      <button
        onClick={() =>
          dispatch({ type: "update", action: { payload: count + 1 } })
        }
      >
        clicked {count}
      </button>
    </div>
  );
}

相同的 useReducer 最后一个参数也是默认值, 通过dispatch方法传入类型然后修改state;

那么如何在 useReducer 里声明多个变量呢?

function Example() {
  const [state, dispatch] = useReducer(
    (state, { type, action }) => {
      switch (type) {
        case "update":
          return { ...state, count: state.count + 1 };
        case "add":
          return { ...state, data: [...state.data, action.payload + 1] };
        default:
          break;
      }
    },
    { count: 0, data: [1, 2, 3] }
  );

  return (
    <div>
      <ul>
        {state.data.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      <button onClick={() => dispatch({ type: "update" })}>
        clicked {state.count}
      </button>
      <button
        onClick={() =>
          dispatch({ type: "add", action: { payload: state.data.length } })
        }
      >
        add
      </button>
    </div>
  );
}

userReducer和结合使用,作用是为了获取当前组件的状值;

4.useEffect

开发过react组件的人都知道,react组件里有很多生命周期,这些生命周期在 function 组件里是怎么实现的呢?这时就要用到 useEffect 了。

首先是 componentDidMount 和 componentDidUpdate 两个生命周期,它俩在function组件里的实现如下:

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("hello world");
  });

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>clicked {count}</button>
    </div>
  );
}

上面代码运行后,可以看到浏览器的console里会打印出 hello world 这个打印就相当于是 componentDidMount 被执行了。

点击button,count每次都会变化,也就是说state一直在变,然后会发现浏览器的 console 里会随着button点击后count变化也会打印 hello world 这时候就相当于 componentDidUpdate 在执行,每次state有变动的时候, useEffect 都会执行一次。

那有没有办法不让它执行呢?有!

useEffect 有两个参数,第一个参数是个函数,第二个参数相当于条件,看一下面的例子:

function Example() {
  const [state, setState] = useState({ count: 0, data: [1, 2, 3] });

  useEffect(() => {
    console.log("hello world");
  }, [state.count]);

  return (
    <div>
      <ul>
        {state.data.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
      <button onClick={() => setState({ ...state, count: state.count + 1 })}>
        clicked {state.count}
      </button>
       
      <button onClick={() => setState({ ...state, data: [...state.data, state.data.length + 1] })}>add</button>
    </div>
  );
}

当页面渲染完后,浏览器console里会有一个 hello world 输出,可以看见我在 useEffect的第二个参数上加了个参数 state.count 这时候它就会以 state.count 为校验目标,只有当 state中的count有变化时,useEffect 里的第一个参数(函数)才会执行。

也就是说当点击第二个按钮的时候,state更新的只是data变量,count变量没有变化,useEffect里的第一个函数参数就不会执行,这第二个参数也就相当于是 shouldComponentUpdate 这个生命周期了。

最后再说一下组件卸载时的生命周期 componentWillUnmount , 要在function组件里实现这个功能也很简单,只需要在 useEffect 里返回一个函数就可以了。

function Example() {
  const [show, setShow] = useState(false);
  return (
    <div className="App">
      {show ? null : <Item />}
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <button onClick={() => setShow(!show)}>show/hidden</button>
    </div>
  );
}

function Item() {
  useEffect(() => {
    console.log("子组件加载");
    // 返回的函数名无所谓,可以随便起
    return function bye() {
      console.log("子组件卸载");    //子组件在卸载时可以在这个函数里进行数据初始化等操作
    };
    // return () => {  // 可以写成匿名函数
    //   console.log("子组件卸载");
    // };
  });
  return <div>我是子组件</div>;
}

当点击按钮时,子组件会交替的显示和不显示,也就意味着子组件在交替的加载和卸载,在浏览器的console里就可以看到 子组件加载 和 子组件卸载 的日志。

5.useMemo

解决的问题:比如一个函数组件中的userState中有多个方法和这些方法对应的属性,其中会写新的一些函数通过某个属性做其他的判断,如果userState的其他方法执行的话,会重新执行这个函数,那么就会造成计算资源浪费,所以通过useMemo把这个函数当成参数传进去作为回调函数,第二个参数是这个函数依赖的数据,这个数据发生变化时再去执行这个回调函数,这个回调函数返回的是一个新的判断条件的值。

import React, { useState, useMemo } from 'react';
 
function Info(props) {
  let [personalInfo , setPersonalInfo] = useState({
    name: 'kevin kang',
    gender: 'male'
  })
 
  function formatGender(gender) {
    console.log('---调用了翻译性别的方法---')
    return gender === 'male' ? '男' : '女'
  }
 
 
  // BAD 
  // 不使用useMemo的情况下,修改其他属性,也会重新调用formatGender方法,浪费计算资源
  // let gender =  formatGender(personalInfo.gender)
 
  // GOOD
  let gender = useMemo(()=>{
    return formatGender(personalInfo.gender)
  }, 
  [personalInfo.gender])
 
  return (
    <div>
      姓名: {personalInfo.name} -- 性别:  { gender } <br/>
      <button onClick={ 
        ()=> { 
          setPersonalInfo({
            ...personalInfo,
            name: 'Will Kang'
          }) 
        }  
      }> 点击修改名字</button>
    </div>
  )
}
 
export default Info

useMemo会对数据进行一个缓存,优化性能;接收两个参数,第一个参数是回调函数,第二个是依赖的数据; 作用:当依赖数据发生变化时,才会调用回调函数,重新计算。

5.useCallback

useCallback起到的作用: 用来避免子组件不必要的重新Render 首先,假如我们不使用useCallback,在父组件中创建了一个名为handleClick的事件处理函数,根据需求我们需要把这个handleClick传给子组件,当父组件中的一些state变化后(这些state跟子组件没有关系),父组件会reRender,然后会重新创建名为handleClick函数实例,并传给子组件,这时即使用React.memo把子组件包裹起来,子组件也会重新渲染,因为props已经变化了,但这个渲染是无意义的。

如何优化呢?这时候就可以用useCallback了,我们用useCallback把函数包起来之后,在父组件中只有当deps变化的时候,才会创建新的handleClick实例,子组件才会跟着reRender(注意,必须要用React.memo把子组件包起来才有用,否则子组件还是会reRender。React.memo是类似于class组件中的Pure.Component的作用)

7.父子组件传值

function组件里父子组件传值很简单,因为它本身就是 function,所以传的值就是方法的参数,如下:

function App() {
  const [data, setData] = useState([1, 2, 3]);
  return (
    <div className="App">
      <Item items={data} />
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <button onClick={() => setData([...data, data.length + 1])}>add</button>
    </div>
  );
}

function Item({ items }) {
  useEffect(() => {
    console.log("子组件加载");
    // 返回的函数名无所谓,可以随便起
    return function bye() {
      console.log("子组件卸载");
    };
    // return () => {  // 可以写成匿名函数
    //   console.log("子组件卸载");
    // };
  });
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}