react hooks基本使用方法

941 阅读9分钟

react hooks

eject

1. eject是讲react-script集合起来,方便了我们运行单页面程序,但如果我们要编译多页面,就需要修改react-script ,但它已经被封装起来不方便改动,所以有了eject,使用eject可以将react-script拆开到项目根目录方便我们修改,react-script拆开后多了webpack的配置文件和一些js代码.

2. eject不能逆向执行,而且没有重复命令

Context(class 组件)

Context在react可以跨组件传递变量,比如从爷爷组件到孙子组件传递变量,尽量少使用context,它会破坏组件的独立性

用法api

  1. createContext

    import React, { Component, createContext } from "react";
    import "./App.css";
    const BatteryContext = createContext();
    class Middle extends React.Component {
      render() {
        return <Leaf />;
      }
    }
    class Leaf extends Component {
      render() {
        return (
          <BatteryContext.Consumer>
            {(battery) => <h1>{battery}</h1>}
          </BatteryContext.Consumer>
        );
      }
    }
    
    class App extends React.Component {
      state = {
        battery: 60,
      };
      render() {
        return (
          <div className="App">
            <BatteryContext.Provider value={this.state.battery}>
              <button
                onClick={() => {
                  this.setState({ battery: this.state.battery - 1 });
                }}
              >
                Press
              </button>
              <Middle></Middle>
            </BatteryContext.Provider>
          </div>
        );
      }
    }
    
    export default App;
    
    

    由于BatteryContext.Consumer 有点繁琐,因此可以用this.context改进,用法如下

    import React, { Component, createContext } from "react";
    import "./App.css";
    const BatteryContext = createContext();
    class Middle extends React.Component {
      render() {
        return <Leaf />;
      }
    }
    class Leaf extends Component {
      static contextType = BatteryContext;//被修改的部分就在这个class中
      
      render() {
        const battery = this.context;
        return <h1>{battery}</h1>;
      }
    }
    
    class App extends React.Component {
      state = {
        battery: 60,
        online: false,
      };
      render() {
        const { online } = this.state;
        return (
          <div className="App">
            <BatteryContext.Provider value={this.state.battery}>
              <button
                onClick={() => {
                  this.setState({ battery: this.state.battery - 1 });
                }}
              >
                Press
              </button>
              <Middle></Middle>
            </BatteryContext.Provider>
          </div>
        );
      }
    }
    
    export default App;
    
    

lazy

mpa是多页面,pwa渐进式应用;spa单页面应用

lazy是将指定组件的导入行为封装成react组件

页面上的图片只有到了图片的位置才去加载,这样可以节省流量

Webpack - Code Splitting 提供了这样一种能力,把一个页面的所有js模块人为划分为多个,分时导入,这样就需要用import

import 有两个应用场景,

  1. import * from ' ' 引用静态资源(最主要的使用场景)
  2. import还有另一个场景,动态导入模块, import(‘./detail.js’).then(…),webpack遇到这个会自动的拆分代码,一般情况下页面不会主动加载,只有使用到了的时候页面才回去加载它
  3. app.jsx
import React, { Component, lazy,Suspense } from "react";
import "./App.css";
// Code Splitting可以支持自定义命名 像这样 /* webpackChunkName:"about"*/ 生成的名字为about.chunk.js
const About = lazy(() => import(/* webpackChunkName:"about"*/"./About")); //lazy返回一个组件
//ErrorBoundary 错误边界
// componentDidCatch在错误边界的应用
class App extends Component {
  state={
    hasError:false
  }
  // componentDidCatch(){//任何组件加载错误时候自动调用
  //   this.setState({
  //     hasError:true
  //   })
  // }
  static getDerivedStateFromError(){//效果和上面类似,出错的时候会用此函数返回的state来setState,写法更加优雅而已
    return {
      hasError:true
    }
  }
  render() {
    //fallback中传入一个组件或者节点
    if(this.state.hasError){//当lazy组件不能加载时就会显示下面的组件
      return <div>出错了</div>
    }
    return (
      <div>
      
        <Suspense fallback={<div>loading</div>}>
          <About></About>
        </Suspense>
      </div>
    );
  }
}

export default App;

About.jsx

import React, { Component } from 'react';
class About extends Component {
  state = {  }
  render() { 
    return ( <div>about</div> );
  }
}
 
export default About;

memo

原理是和class组件一样

解决react运行的效率问题

避免重新渲染的几种方式

  1. import React, { Component,PureComponent} from "react";
    
    class Foo extends Component {
      shouldComponentUpdate(nextProps, nextState) { //避免不必要的重新渲染,只有props改变了才重新渲染
        if(nextProps.name===this.props.name){
          return false;
        }else{
          return true
        }
      
      }
      render() { 
        console.log('foo render')
        return ( null );
      }
    }
     
    
    class App extends Component {
      state = {count:0}
      render() { 
        const {count} = this.state;
        return ( <div>
        <button onClick={()=>{this.setState({count:count+1})}}>add</button>
        <Foo name="Mike"></Foo> 
        </div>
        );
      }
    }
     
    export default App;
    
  2. PureComponent

    import React, { Component, PureComponent } from "react";
    
    class Foo extends PureComponent {
      //用了pureCompoent可以实现不必要的更新,只有props变化才会重新渲染,比如父组件更新count但是Foo不会更新
      //有局限,这个是浅比较,如果传入props内部属性变化不会重新渲染
      render() {
        console.log("foo render");
        return <div>{this.props.person.age}</div>;
      }
    }
    
    class App extends Component {
      state = { count: 0, person: { age: 1 } };
      render() {
        const { count,person } = this.state;
        return (
          <div>
            <button
              onClick={() => {
                this.setState({ count: count + 1 });
                person.age+=1;
                this.setState({
                  person
                })
              }}
            >
              add
            </button>
            <Foo name="Mike" person={person}></Foo>
            <div>count:{count}</div>
          </div>
        );
      }
    }
    
    export default App;
    
    

    如果Foo传入匿名回调函数在父组件更新的时候Foo也会更新,因为匿名函数的地址可能不一样,如下所示

    <Foo name="Mike" person={person} cb={()=>{}}></Foo>
    

    如果传入的不是匿名函数而是已经定义好的函数就不会有这种情况,如下所示

    <Foo name="Mike" person={person} cb={this.callback}></Foo>
    

    memo原理就是上面this.callback的方式来保持组件不更新,不同的是memo用于函数组件中

memo用法

import React, { Component, memo } from "react";

  //更改为function组件后的代码
  const Foo = memo(function Foo(props) {
    console.log("foo render");
    return <div>{this.props.person.age}</div>;
  })


class App extends Component {
  state = { count: 0, person: { age: 1 } };
  callback(){}
  render() {
    const { count,person } = this.state;
    return (
      <div>
        <button
          onClick={() => {
            this.setState({ count: count + 1 });
            person.age+=1;
            this.setState({
              person
            })
          }}
        >
          add
        </button>
        <Foo name="Mike" person={person} cb={this.callback}></Foo>
        <div>count:{count}</div>
      </div>
    );
  }
}

export default App;

用法几乎一样,只是把

class Foo extends PureComponent {
  //用了pureCompoent可以实现不必要的更新,只有props变化才会重新渲染,比如父组件更新count但是Foo不会更新
  //有局限,这个是浅比较,如果传入props内部属性变化不会重新渲染
  render() {
    console.log("foo render");
    return <div>{this.props.person.age}</div>;
  }
}

变成了

 const Foo = memo(function Foo(props) {
    console.log("foo render");
    return <div>{this.props.person.age}</div>;
  })

hooks

![image-20200413160337724](react hooks.assets/image-20200413160337724.png)

![image-20200413160452789](react hooks.assets/image-20200413160452789.png)

![image-20200413160510881](react hooks.assets/image-20200413160510881.png)

![image-20200413160525574](react hooks.assets/image-20200413160525574.png)

![image-20200413160736757](react hooks.assets/image-20200413160736757.png)

副作用在这里不是坏的,是指除了渲染之外的操作,比如数据持久化,http请求,绑定解绑事件

useState

每次渲染要执行相同次数的useState 和class的state差不多,当传入函数时只在组件第一次渲染执行

const [count, setCount] = useState(() => {
    console.log("initial count");
    return 0;
  });
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
  });

useEffect

相当于一个ComponentWillMount和ComponentWillUnmount

useEffect(() => {
    console.log(size)
    window.addEventListener("resize", onResize, false);
    return () => {
      window.removeEventListener("resize", onResize, false);
    };
  },[]);

useEffect的两个特例,第二个参数不传是每次渲染都会更新,传空数组就只更新一次.

完整代码

import React from "react";
import { useState, useEffect } from "react";
// class App extends Component {
//   state = { count:0 }
//   render() {
//     const {count} = this.state;
//     return ( <button onClick={()=>{this.setState({count:count+1})}}>
//       count:{count}
//     </button> );
//   }
// }

function App() {
  const [count, setCount] = useState(() => {
    console.log("initial count");
    return 0;
  });
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
  });
  const onResize = () => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
    });
  };
  useEffect(() => {
    console.log('count change')
    document.title = count;
  });
  useEffect(() => {
    console.log(size)
    window.addEventListener("resize", onResize, false);
    return () => {
      window.removeEventListener("resize", onResize, false);
    };
  },[]);
  return (
    <button
      onClick={(a) => {
        setCount(count + 1);
      }}
    >
      Click:({count})
      width:({size.width})xheight:({size.height})
    </button>
  );
}
export default App;

Context Hooks(function组件) 和useContext

类组件的两种context

  1. consumer
  2. contextType

function组件有一种,useContext()

对比:

- Consumer可以多层嵌套,可以使用到多个context的值,但是使用不优雅
- contextType只有一个 所以一个类组件只能拿到一个context值,使用比较方便
- 函数组件的useContext可以获取多个context的值,使用方便优雅

例子:

import React from "react";
import { useState, createContext,Component,useContext } from "react";

const countContext = createContext();
class Foo extends Component {
  render() { 
    //Consumer可以多层嵌套,可以使用到多个context的值,但是使用不优雅
    return ( 
      <countContext.Consumer>
        {count=><h1>{count}</h1>}
      </countContext.Consumer>
     );
  }
}
class Bar extends Component {
  static contextType = countContext; 
  //contextType只有一个 所以一个类组件只能拿到一个context值
  render() { 
   const count = this.context;
    return ( <h1>{count}</h1> );
  }
}
function Counter(){
  const count = useContext(countContext);
  //函数组件可以获取多个context的值,使用方便优雅
  return ( <h1>
    {count}
  </h1> )
} 

function App() {
  const [count, setCount] = useState(() => {
    console.log("initial count");
    return 0;
  });
  return (
    <div>
      <button
        onClick={(a) => {
          setCount(count + 1);
        }}
      >
        Click:({count})
      </button>
      <countContext.Provider value={count}>
        <Foo></Foo>
        <Bar></Bar>
        <Counter></Counter>
      </countContext.Provider>
    </div>
  );
}
export default App;

Memo 和 Callback Hooks (useCallback)

useMemo是针对一个函数逻辑是否重复执行

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

useMemo和useEffect类似,第一个参数是要执行的逻辑函数,第二个参数是依赖的变量数组

useMemo会执行函数并返回值(如果有return值)

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

function Counter(props) {
  //函数组件可以获取多个context的值,使用方便优雅
  return <h1>{props.count}</h1>;
}

function App() {
  const [count, setCount] = useState(() => {
    console.log("initial count");
    return 0;
  });
  const double = useMemo(()=>{ //useMemo会执行函数返回count的两倍
    return count*2;
  },[count===3])
  //第三次点击count=3结果为true,重新计算 double=6,第一第二次double=0,第四次结果为false又重新计算,结果为8
  return (
    <div>
      <button
        onClick={(a) => {
          setCount(count + 1);
        }}
      >
        Click:({count}) double:({double})
      </button>

      <Counter count={count}></Counter>
    </div>
  );
}
export default App;

如果useMemo返回的是一个函数,那么等价于useCallback.作用是使函数组件重新渲染时内部的函数不发生变化

useMemo(()=>fn) === useCallback(fn)

import React from "react";
import { useState,useMemo,memo } from "react";
import { useCallback } from "react";

const Counter = memo(function Counter(props) {
  console.log('Counter render')
  //函数组件可以获取多个context的值,使用方便优雅
  return <h1 onClick={props.onClick}>{props.count}</h1>;
})

function App() {
  console.log('app render')
  const [count, setCount] = useState(() => {
    console.log("initial count");
    return 0;
  });
  const double = useMemo(()=>{ //useMemo会执行函数返回count的两倍
    return count*2;
  },[count===3])
  const onClick=useCallback(()=>{
    console.log('click');
  },[])
  const half = useMemo(()=>{ //useMemo会执行函数返回count的两倍
    return double/4;
  },[double])
  //第三次点击count=3结果为true,重新计算 double=6,第一第二次double=0,第四次结果为false又重新计算,结果为8
  return (
    <div>
      <button
        onClick={(a) => {
          setCount(count + 1);
        }}
      >
        Click:({count}) double:({double}) half:({half})
      </button>

      <Counter count={double} onClick={onClick}></Counter>
    </div>
  );
}
export default App;

Ref hooks

useRef其中的一个使用场景是调用子组件的函数

import React from "react";
import { useState,useMemo,memo,PureComponent } from "react";
import { useCallback,useRef } from "react";

// const Counter = memo(function Counter(props) {
//   console.log('Counter render')
//   //函数组件可以获取多个context的值,使用方便优雅
//   return <h1 onClick={props.onClick}>{props.count}</h1>;
// })
class Counter extends PureComponent{
  speak(){
    console.log('now counter is '+this.props.count)
  }
  render(){
    const {props} = this;
    console.log('Counter render')
    return <h1 onClick={props.onClick}>{props.count}</h1>; 
  }
}
function App() {
  console.log('app render')
  
  const [count, setCount] = useState(() => {
    console.log("initial count");
    return 0;
  });
  const CounterRef = useRef()
  const double = useMemo(()=>{ //useMemo会执行函数返回count的两倍
    return count*2;
  },[count===3])
  const onClick=useCallback(()=>{
    console.log('click');
    CounterRef.current.speak()
  },[CounterRef])
  const half = useMemo(()=>{ //useMemo会执行函数返回count的两倍
    return double/4;
  },[double])
  //第三次点击count=3结果为true,重新计算 double=6,第一第二次double=0,第四次结果为false又重新计算,结果为8
  return (
    <div>
      <button
        onClick={(a) => {
          setCount(count + 1);
        }}
      >
        Click:({count}) double:({double}) half:({half})
      </button>

      <Counter ref={CounterRef} count={double} onClick={onClick}></Counter>
    </div>
  );
}
export default App;

useRef的另一个作用是保持一个常量不变,比如定时器的id,原本it会随着app的重新渲染会重新定义it,但是如果使用useRef他就能在app重新渲染时保持同一个句柄

import React from "react";
import { useState,useMemo,memo,PureComponent } from "react";
import { useCallback,useRef } from "react";
import { useEffect } from "react";

// const Counter = memo(function Counter(props) {
//   console.log('Counter render')
//   //函数组件可以获取多个context的值,使用方便优雅
//   return <h1 onClick={props.onClick}>{props.count}</h1>;
// })
class Counter extends PureComponent{
  speak(){
    console.log('now counter is '+this.props.count)
  }
  render(){
    const {props} = this;
    console.log('Counter render')
    return <h1 onClick={props.onClick}>{props.count}</h1>; 
  }
}
function App() {
  console.log('app render')
  let it = useRef();
  const [count, setCount] = useState(() => {
    console.log("initial count");
    return 0;
  });
  const CounterRef = useRef()
  const double = useMemo(()=>{ //useMemo会执行函数返回count的两倍
    return count*2;
  },[count===3])
  const onClick=useCallback(()=>{
    console.log('click');
    CounterRef.current.speak()
  },[CounterRef])
  const half = useMemo(()=>{ //useMemo会执行函数返回count的两倍
    return double/4;
  },[double])
  useEffect(()=>{
    it.current = setInterval(()=>{setCount(count=>count+1)},1000)
  },[])
  useEffect(()=>{
    if(count>10){
      clearInterval(it.current)
    }
  })
  //第三次点击count=3结果为true,重新计算 double=6,第一第二次double=0,第四次结果为false又重新计算,结果为8
  return (
    <div>
      <button
        onClick={(a) => {
          setCount(count + 1);
        }}
      >
        Click:({count}) double:({double}) half:({half})
      </button>

      <Counter ref={CounterRef} count={double} onClick={onClick}></Counter>
    </div>
  );
}
export default App;

自定义hooks

优点是可复用性强,比如下面的useSize,既可以在app中用,也可以在couter子组件中使用

import React from "react";
// eslint-disable-next-line 
import { useState,useMemo,memo,PureComponent } from "react";
// eslint-disable-next-line 
import { useCallback,useRef } from "react";
// eslint-disable-next-line 
import { useEffect } from "react";
function useCount(defaultCount){
  const [count, setCount] = useState(defaultCount);
  const it = useRef()
  useEffect(()=>{
    it.current = setInterval(()=>{
      setCount(count=>count+1)
    },1000)
  },[])
  useEffect(()=>{
    if(count>10){
      clearInterval(it.current)
    }
  })
  return [count,setCount]
}

function useCounter(count){
  const size = useSize()
  return <h1>{count}sizeWidth{size.width}</h1>; 
}
function useSize(){
  const [size,setSize] = useState({
    width:document.documentElement.clientWidth,
    height:document.documentElement.clientHeight
  })
  const onResize = useCallback(()=>{
    setSize({
      width:document.documentElement.clientWidth,
      height:document.documentElement.clientHeight
    })
  },[])
  useEffect(()=>{
    window.addEventListener('resize',onResize,false)
    return ()=>window.removeEventListener('resize',onResize)
  },[])
  return size;
}
function App() {
  console.log('app render')
  let [count,setCount]=useCount(0)
  const Counter = useCounter(count)
  const size = useSize();
 //第三次点击count=3结果为true,重新计算 double=6,第一第二次double=0,第四次结果为false又重新计算,结果为8
  return (
    <div>
      <button
        onClick={(a) => {
          setCount(count + 1);
        }}
      >
        Click:({count})
      </button>
        {size.width}x{size.height}
      {Counter}
    </div>
  );
}
export default App;