前端 - React Hook

114 阅读10分钟

Hook官网 react.docschina.org/docs/hooks-…

useState

  1. useState的快速使用:
  • 1.从 Raect 导入 useState 函数
  • 2.执行这个函数并且传入初始值,必须在函数组件中使用
  • 3.[数据,修改数据的方法]
  • 4.使用数据,修改数据
import React,{useState} from 'react';

function App() {
  const [count,setCount] = useState(0);
  
  return (
    <div className="App">
      this is app...
      <span>{count}</span>
      <button onClick={()=>setCount(count+10)}>修改count</button>
    </div>
  );
}

export default App;

2.useState状态的读取和修改:

  • const [count,setCount] = useState(0);
  • 1.useState传过来的参数0,作为 count 的初始值,这个初始值只在第一次执行的时候生效。
  • 2.[count,setCount] 这里的写法是一个解构赋值,useState的返回值是一个数组。
    • count,setCount名字可以自定义,但是需要保证语义化。
    • count,setCount名字顺序不可以换,第一个参数就是数据状态,第二个参数就是修改数据的方法。(对象的解构赋值是无序的,通过key,value的形式取。数组的解构赋值是有序的。)
  • 3.setCount 函数,作用是用来修改count ,依旧保持不能直接修改原值,还是生成一个新值替换原值。
    • setCount是基于原值计算得到的新值。
  • 4.count和setCount是一对,是绑定在一起的,setCount只能用来修改对应的count值。
import React,{useState} from 'react';

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

  return (
    <div className="App">
      this is app...
      <span>{count}</span>
      <button onClick={()=>setCount(count+10)}>修改count</button>
    </div>
  );
}

export default App;

3.组件的更新过程(当调用setCount的时候,更新过程):

  • 1.首次渲染
    • 首次渲染的时候,App组件内部的代码会被执行一次,其中useState也会跟着执行,这里重点注意:初始值 useState(0) 只在首次渲染时生效。
  • 2.更新渲染(只要调用setCount都会更新)
    • App组件会再次渲染,setCount这个函数会再次执行
    • useState再次执行,得到的新的count值不是0而是修改之后的1,模板会用新值渲染。
  • 小结:useState初始值只在首次渲染生效,后续只要调用setCount,整个App组件中的代码都会执行,此时的count每次拿到的都是最新值。
import React,{useState} from 'react';

function App() {
  const [count,setCount] = useState(0);
  console.log(count);// 更新渲染的时候,每次都是count的最新值

  return (
    <div className="App">
      this is app...
      <span>{count}</span>
      <button onClick={()=>setCount(count+10)}>修改count</button>
    </div>
  );
}

export default App;

4.使用react_dev_tools查看最新的useState定义的状态值 4.使用react_dev_tools查看最新的useState定义的状态值.png 5.useState的使用规则:

  • 1.useState函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态。(App组件可以通过useState定义多个初始值的状态)
  • 2.useState只能出现在App组件里面,不能if/for/其他函数中
  • 3.可以通过react_dev_tools开发者工具查看hook状态
import React,{useState} from 'react';

function App() {
  const [count,setCount] = useState(0);
  console.log(count);// 更新渲染的时候,每次都是count的最新值

  const [flag,setFlag] = useState(true);
  const [list,setList] = useState([]);

  const test = () =>{
    setCount(count+10);
    setFlag(false);
    setList([1,2,3])
  }
  return (
    <div className="App">
      this is app...
      <div>count:{count}</div>
      <div>flag:{flag?'1':'0'}</div>
      <div>list:{list.join('-')}</div>
      <button onClick={()=>test()}>修改count</button>
    </div>
  );
}

export default App;
  1. 将一个回调函数作为useState的参数的使用场景:
  • 参数只会在组件初始化渲染的时候起作用,后续渲染时会被忽略。
  • 如果初始化的state需要通过计算才能获得,则可以传入一个回调函数,在函数中计算并返回初始值的state,此函数只在初始化渲染时被调用。
  • 需求:如何实现一个自增按钮,可以由使用的时候以传参的方式决定递增的初始值?
import React,{useState} from 'react';
function Counter(props){
  const [count,setCount] = useState(()=>{
    /*
      这里的目的是为了体现初始值经过一定的计算,但是这个计算是一个比较广义的概念。
      只要无法直接确定,需要通过一定的操作才能获取,就可以理解为计算。 
     */
    
    // 编写计算逻辑,return '计算之后的初始值'
    return props.count
  });
  const handle = () =>{
    setCount(count+10)
  }
  return (
    <button onClick={handle}>{count}</button>
  )
}

function App(){
  return (
    <div>
      <Counter count={10}/>
      <Counter count={22}/>
    </div>
  )
}
export default App;
  • useState将回调函数作为参数示例
import React,{useState} from 'react';
function getDefaultValue(){
  for(let i=0;i<10000;i++){

  }
  return 10
}
function Counter(props){
  const [count,setCount] = useState(()=>{
    /*
      这里的目的是为了体现初始值经过一定的计算,但是这个计算是一个比较广义的概念。
      只要无法直接确定,需要通过一定的操作才能获取,就可以理解为计算。 
     */

    // 编写计算逻辑,return '计算之后的初始值'
    // return props.count

    return getDefaultValue()
  });
  const handle = () =>{
    setCount(count+10)
  }
  return (
    <button onClick={handle}>{count}</button>
  )
}

function App(){
  return (
    <div>
      <Counter/>
    </div>
  )
}
export default App;

useEffect

useEffect常见的副作用:

  • 1.数据请求。
  • 2.手动修改DOM。
  • 3.localstorage操作。 1.useEffect的简单使用(需求:在修改数据之后,把count值放到页面标题中。)
  • 1.导入useEffect函数。
  • 2.在函数组件中执行,传入回调,并且定义副作用。
  • 3.当我们通过修改状态更新组件时,副作用也会不断执行。
import React,{useEffect, useState} from 'react';
function App(){
  const [count,setCount] = useState(0);
  const handle = () =>{
    setCount(count+10)
  }
  useEffect(()=>{
    document.title = count
  })
  return (
    <div>
      this is App 。。。
      <div>
         <button onClick={handle}>{count}</button>
      </div>
    </div>
  )
}
export default App;

2.通过依赖项控制副作用的执行时机:

  • 1.默认状态(无依赖项)
    • 组件初始化的时候先执行一次,等到每次数据修改组件更新时再次执行。
  • 2.添加一个空数组依赖项
    • 只会在组件初始化的时候执行一次。
  • 3.依赖特定项
    • 组件初始化的时候执行一次,依赖的特定项发生变化会再次执行。
  • 4.注意事项:
    • 只要在useEffect回调函数中用到的数据状态,就应该出现在依赖项数组中声明,否则可能会有bug。
    • 某种意义上,hook的出现,就是想不用类组件的生命周期的概念,也可以写业务代码。
import React,{useEffect, useState} from 'react';
function App(){
  const [count,setCount] = useState(0);

  const [name,setName] = useState('xyh');

  const handleCount = () =>{
    setCount(count+10)
  }

  const handleName = () =>{
    setName('jhh')
  }

  // 1.默认无依赖项
  // useEffect(()=>{
  //   console.log('操作DOM');
    
  //   //定义副作用
  //   document.title = count
  // })

  // 2.添加空数组为依赖项
  // useEffect(()=>{
  //   console.log("依赖项是空数组");

  //   document.title = 'useEffect'
  // },[])

  // 3.特定依赖项
  // useEffect(()=>{
  //   console.log('特定依赖项');
  //   document.title=count
  // },[count])

  //4.注意事项:例如:回调函数中使用了name,就应该把name定义到依赖项里面。    
  useEffect(()=>{
    console.log('特定依赖项');
    document.title=count
    console.log(name)
  },[count,name]); // 该副作用函数什么时候会执行?初始化的时候执行一次,当count/name被修改时都会在执行一次。

  return (
    <div>
      this is App 。。。
      <div>
         <button onClick={handleCount}>{count}</button>
         <button onClick={handleName}>{name}</button>
      </div>
    </div>
  )
}
export default App;
  1. useEffect清理副作用(componentWillUnmount)的使用场景:
  • 在组件被销毁时,有些副作用操作需要被清理,例如:定时器
import React,{useState,useEffect} from 'react';
function Test(){
  useEffect(()=>{
    let timer = setInterval(()=>{
      console.log('定时器开始执行');
    },1000);

    // 清除定时器
    return ()=>{
      clearInterval(timer)
    }
  },[])// 依赖项为空数组,只会执行一次
  return <div>xxx</div>
}
function App(){
  const [flag,setFlag] = useState(true);
  const handleFlag = () =>{
    setFlag(!flag)
  }
  return (
    <div>
      {flag?<Test/>:null}
      <button onClick={handleFlag}>switch</button>
    </div>
  )

}
export default App;

4.useEffect发送网络请求

  • 类组件如何发送网络请求?
    • 通过componentDidMount生命周期钩子函数。
  • componentDidMount生命周期的执行时机?
    • 在初始化的时候,DOM渲染完毕只执行一次。 useEffect:
  • 1.不加依赖项,初始化渲染和状态更新都会执行
  • 2.加 [] , 只会在初始化时执行一次。
  • 3.加特定的依赖项[count,name],初始化时执行和count、name任意一个变化执行
import React,{useEffect} from 'react';
function App(){
  useEffect(()=>{
    // 真实发送请求的写法:
    // async function loadData(){
    //   const res = await fetch('http://geek.itheima.net/v1_0/channels');
    //   console.log(res);
    // }
    // loadData();

    //查看学习Fetch API的写法:
    function loadData(){
      fetch('http://geek.itheima.net/v1_0/channels').then(
        res=>res.json()
      ).then(data=>console.log(data))

    }
    loadData()
  },[])
  return (
    <div>
      this is app ...

    </div>
  )
}
export default App;

9.useEffect发送网络请求.png

useRef

useRef:

  • 作用:useRef可以获取真实DOM或组件实例。(组件实例是指类组件,DOM对象是标签)
  • 使用场景:在函数组件中获取真实DOM元素对象或者是组件对象。
  • 使用步骤:
    • 1.导入useRef函数。
    • 2.执行useRef函数并传入null,返回值为一个对象内部有一个current属性存放拿到的DOM对象(或者组件实例)
    • 3.通过ref绑定要获取的元素或者组件。 注意:useEffect回调函数是在DOM渲染完成之后执行。
import React,{useEffect, useRef} from 'react';
// 组件实例是指类组件
class Test extends React.Component{
  state = {
    name:'我是类组件(组件实例)'
  }
  getName = () =>{
    return 'this is classTest'
  }
  render(){
    return (
      <div>
        {this.state.name}
      </div>
    )
  }
}

function App(){
  const testRef = useRef(null);
  const hRef = useRef(null);

  // useEffect回调函数是在DOM渲染完成之后执行。
  useEffect(()=>{
    console.log(testRef.current);
    console.log(hRef.current);
  },[])

  return (
    <div>
      <Test ref={testRef}/>
      <h1 ref={hRef}>this is h1...</h1>
    </div>
  )
}
export default App;

useContext

useContext的使用步骤:

  • 1.使用createContext创建Context对象
  • 2.在顶层组件通过Provider提供数据
  • 3.在底层组件通过useContext函数获取数据
import React,{createContext,useContext, useState} from 'react';
const Context = createContext();//可以得到一个对象
function ComC(){
  const count = useContext(Context)
  return (
   <div>
    this is ComC。。。
    App组件传入:{count}
   </div> 
  )
}
function ComA(){
  const count = useContext(Context);
  return (
    <div>
      this is ComA。。。
      App组件传入:{count}
      <ComC/>

    </div> 
   )
}

function App(){
  const [count,setCount] = useState(1688);

  // handCount修改状态
  const handCount = () =>{
    setCount(count+100)
  }

  return (
    <Context.Provider value={count}>
      <div>
        <ComA/>
        <button onClick={handCount}>改变count</button>
      </div>
    </Context.Provider>
    
  )
}
export default App;

useMemo

  • useMemo 记忆函数
const fn = useMemo(() => {
    return () => { console.log('fn') }
}, []);

useCallback

  • useCallback用来记忆函数的,当需要记忆的对象是一个函数的时候:
const fn = useCallback(() => {
    console.log('fn')
}, []);

useReducer

useReducer 配合 useContext 可以替代 redux。

// 1.导入useReducer
import React, { useReducer } from "react";

// 2.创建初始值
const initial = {
   n: 0
};

// 3.创建所有操作
const reducer = (state, action) => {
   if (action.type === "add") {
  		 return { n: state.n + action.number };
   } else if (action.type === "multi") {
  		 return { n: state.n * action.number };
   } else {
  		 throw new Error("unknown type");
   }
};

function App() {
  // 4.将reducer 和 initial 传给useReducer,得到读和写的api:state 和 dispatch
   const [state, dispatch] = useReducer(reducer, initial);
   const { n } = state;
   const AddHandle = () => {
      // 5-1.调用dispatch
   		dispatch({ type: "add", number: 1 });
   };
   const MultiHandle = () => {
       // 5-2.调用dispatch
  		 dispatch({ type: "multi", number: 2 });
   };
   return (
       <div className="App">
           <h1>n: {n}</h1>

           <button onClick={AddHandle}>+1</button>
           <button onClick={MultiHandle}>x2</button>
       </div>
   );
}
export default App;

forwardRef

useLayoutEffect

useImperativeHandle

自定义hook

  • 提取组件逻辑,实现复用,优化代码。
  • 案例一:
    • 需求:自定义一个hook函数,实现获取滚动距离Y。

      • const [y] = useWindowScroll(); // y就是滚动到顶部的距离
          import React from 'react';
          import {useWindowScroll} from './hooks/1.useWindowScroll'
          function App(){
            const [y] = useWindowScroll();
            return (
              <div style={{height:'12000px'}}>
                this is App ...
                {y}
              </div>
            )
          }
          export default App;
      
      import {useState} from 'react';
      
      export function useWindowScroll(){
      
          const [y,sety] = useState(0);
      
          // 实现思路:在滚动行为发生的时候,不断获取滚动值,然后交给y
          window.addEventListener('scroll',()=>{
              const h = document.documentElement.scrollTop;//可以拿到当前顶部的距离
              sety(h)
          });
          return [y]
      }
      
      

      1.useWindowScroll.png

  • 案例二:
    • 需求:自定义hook函数,可以自动同步到本地LocalStorage。
          import React from 'react';
          import {useLocalStorage} from './hooks/2.useLocalStorage';
          function App(){
            const [message,setMessage] = useLocalStorage('hook-key','小辉');
            setTimeout(()=>{
              setMessage("小明")
      
            },5000)
            return (
              <div>
                {message}
              </div>
            )
          }
      
          export default App;
          
      
          import React,{useState,useEffect} from 'react'
          export function useLocalStorage(key,defaultValue){// key是存储数据的,defaultValue是默认值
              const [message,setMessage] = useState(defaultValue);
      
              // 每次只要message变化,就会自动同步到本地localStorage(加入useEffect副作用,本地存储)
              useEffect(()=>{
                  window.localStorage.setItem(key,message)
              },[message,key])
      
              return [message,setMessage]
      
          }
      
      2.useLocalStorage.png

1.useRef 保存数据和 useState 保存数据的区别:

    1. 与视图相关的请用 useState 保存,因为只有 setState 会驱动视图的更新,而改变 useRef() 对象是不会改变视图的。
    1. 保存的对象需要在生命周期中唯一(单例),请使用 useRef,因为 useState 每次更新会重新生成一个对象。
    1. 如果想改变某个值后,同步获取更新后的值,请使用 useRef,因为 useState 的改变不是同步的,要在下一次 render 后生效。

2.React.memo 和 useMemo

  • 使用React.memo的时候,如果父组件传给子组件的是一个对象,父组件重新渲染后,父组件中要传给子组件的对象也会重新生成,空间地址也就和之前的不一样了,所以子组件也会重新渲染,此时React.memo的作用就失效了。
  • 父组件如果传给子组件的是对象,React.memo失效的解决方法?
    • 用 useMemo 记忆一下这个对象,使它的内存地址保持不变。
        //☆useMemo 记忆对象props保持内存不变
        import React, { useState, useMemo } from "react";
    
        function App() {
          const [n, setN] = useState(0);
          const [m, setM] = useState(1000);
    
          const handleN = () => {
            setN((n) => n + 10);
          };
    
          // const data2 = { a: 1 };
          const data2 = useMemo(() => {
            return { age: 18 };
          }, []);
    
          return (
            <div className="App">
              <button onClick={handleN}>update n {n}</button>
              <NewChild data={m} data2={data2} />
            </div>
          );
        }
    
        function Child(props) {
          console.log("child 执行了");
          console.log("假设这里有大量代码");
          return <div>
            child: {JSON.stringify(props.data2.age)}
            <div>{props.data}</div>
          </div>;
        }
    
        const NewChild = React.memo(Child);
        export default App;