react新特性hooks使用详细教程

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

为什么使用hooks?

  • 在组件之间复用状态逻辑很难,可能要用到render props和高阶组件,React 需要为共享状态逻辑提供更好的原生途径,Hook 使你在无需修改组件结构的情况下复用状态逻辑
  • 复杂组件变得难以理解,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • 难以理解的 class,包括难以捉摸的this

使用hooks注意点

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用

更详细的了解可以看下官网

1.useState使用

//组件函数每次渲染都会被调用,每次执行都产生一个独立的闭包
import React,{useState,useEffect} from 'react';
import {render} from 'react-dom';
function Home(){
    let [count1,setCount1] = useState(0);
    function alertCount1(){
        setTimeout(()=>{
          alert(count1);
        },3000);
      }
    return <div>
        <p>count1: {count1}</p>
        <button onClick={()=>setCount1(count1+1)}>count1+</button>
        <button onClick={alertCount1}>alertCount1</button>
    </div>
}

render(<Home />,window.root);

复制代码

2.useEffect使用

  • 不需要清除的副作用
import React,{useState,useEffect} from 'react';
import {render} from 'react-dom';
function Home(){
    let [count1,setCount1] = useState(0);
    //如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
    useEffect(()=>{
        document.title = `这事第${count1}点击`;
    })
    return <div>
        <p>count1: {count1}</p>
        <button onClick={()=>setCount1(count1+1)}>count1+</button>
    </div>
}

render(<Home />,window.root);

复制代码
  • 需要清除的副作用

下面每次count1加1都没重新开启一个新的定时器所以就会越加越快, 这次需要清除副作用。useEffect可以返回一个函数,该函数会在组件卸载或者重新re-render前执行。

缺点是: 连续点击count1加时,就会多次创建定时器和清除定时器。也不会打印11111。

import React,{useState,useEffect} from 'react';
import {render} from 'react-dom';
function Home(){
    let [count1,setCount1] = useState(0);
    //如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
    useEffect(()=>{
       let timer = setInterval(()=>{
           console.log(11111);
       },1000);
       return ()=>{
          clearInterval(timer);
       }
    })
    return <div>
        <p>count1: {count1}</p>
        <button onClick={()=>setCount1(count1+1)}>count1+</button>
    </div>
}

render(<Home />,window.root);
复制代码

useEffect第二个参数是依赖项。如果设置空数组则不依赖任何参数。所以re-render时候就不会执行useEffect函数。此时useEffect返回函数只会在组件卸载的时候执行。

import React,{useState,useEffect} from 'react';
import {render} from 'react-dom';
function Count1(){
    let [count1,setCount1] = useState(0);
    //如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
    useEffect(()=>{
       let timer = setInterval(()=>{
           console.log(11111);
       },1000);
       return ()=>{
          clearInterval(timer);
       }
    },[])
    return <div>
        <p>count1: {count1}</p>
        <button onClick={()=>setCount1(count1+1)}>count1+</button>
    </div>
}

function Home(){
    let [isShow,setIsShow] = useState(false);
    //如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
    let changeShow = ()=>{
        setIsShow(!isShow);
    }
    return <div>
        <button onClick={changeShow}>show</button>
        {isShow&&<Count1></Count1>}
        
    </div>
}

render(<Home />,window.root);
复制代码

3.useReducer使用

  • useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
  • 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
import React,{useState,useEffect,useReducer} from 'react';
import {render} from 'react-dom';

let initCount = 1;
function countRender(state,action){
    switch(action.type){
        case 'ADD':
            return state+action.num;
        case 'REDUCE':
            return state-action.num;
        default:
            return  state;     
    }
}


function Home(){
    //这里熟悉redux用法的会有种亲切的赶脚
    //useReducer 三个参数 
    //第一个reducer
    //第二个initialState 第三个参数的时候直接作为初始化数据,有第三个参数的时候作为第三个参数函数的入参。
    //第三个 initializer 惰性初始化函数返回初始化数据 
    let [count1,dispatch] = useReducer(countRender,initCount,(state)=>10+state);
    return <div>
        <p>count1: {count1}</p>
        <button onClick={()=>dispatch({
            type:"ADD",
            num:3
        })}>ADD</button>
         <button onClick={()=>dispatch({
            type:"REDUCE",
            num:2
        })}>REDUCE</button>
    </div>
}


render(<Home />,window.root);
复制代码

4.useRef使用

  • 组件内使用
function Home(){
    let myinput = useRef();
    let submit = ()=>{
        console.log(myinput.current.value);
    }
    return <div>

        <input ref={myinput} type="text"/>
        <button  onClick={submit}>提交</button>
    </div>
}
复制代码
  • 父子组件转发
import React,{useState,useEffect,useRef,useImperativeHandle} from 'react';
import {render} from 'react-dom';

function Home(){
    let myinput1 = useRef();
    let myinput2 = useRef();
    let setInputs1 = ()=>{
        myinput1.current.setValue("hello world");
        myinput1.current.focus();
    }
    let setInputs2 = ()=>{
        myinput2.current.value = "hello";
    }
    return <div>
        <button  onClick={setInputs1}>设置1</button>
        <WithRefMyInputs1 ref={myinput1}/>
        <button  onClick={setInputs2}>设置2</button>
        <WithRefMyInputs2 ref={myinput2}/>
    </div>
}

function MyInputs1(props,ref){
    let focusInput = useRef();
    let valInput = useRef();
    //自组件可以只暴露部分操作。
    useImperativeHandle(ref,()=>({
        focus(){
            focusInput.current.focus();
        },
        setValue(val){
            valInput.current.value =  val;
        }
    }))
    return <div>
        <div>
            获取焦点: <input ref={focusInput}/>
        </div>
        <div>
            设置初始值: <input ref={valInput}/>
        </div>
    </div>
}
function MyInputs2(props,ref){
    //子组件功能全部暴露
    return <div>
        <div>
            设置初始值: <input ref={ref}/>
        </div>
    </div>
}
let WithRefMyInputs1 = React.forwardRef(MyInputs1);
let WithRefMyInputs2 = React.forwardRef(MyInputs2);
render(<Home />,window.root);
复制代码

5.useLayoutEffect使用

  • 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
  • 可以使用它来读取 DOM 布局并同步触发重渲染、
  • 在浏览器执行绘制之前useLayoutEffect内部的更新计划将被同步刷新
  • 尽可能使用标准的 useEffect 以避免阻塞视图更新
import React,{useState,useEffect,useLayoutEffect} from 'react';
import {render} from 'react-dom';
function LayoutEffect() {
    const [color, setColor] = useState('red');
    //用法同useEffect 只是执行时机不同
    useLayoutEffect(() => {
        alert(color);
    });
    useEffect(() => {
        console.log('color', color);
    });
    return (
        <>
            <div style={{ background: color }}>颜色</div>
            <button onClick={() => setColor('red')}>红</button>
            <button onClick={() => setColor('yellow')}>黄</button>
            <button onClick={() => setColor('blue')}>蓝</button>
        </>
    );
}
render(<LayoutEffect />,window.root);
复制代码

6.自定义hook

  • 通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
//实现多个不同时间的倒计时功能
import React,{useState,useEffect,useLayoutEffect} from 'react';
import {render} from 'react-dom';

function formate(diffs){
    let h  = Math.floor(diffs/3600); 
    let m = Math.floor((diffs%3600)/60);
    let s = diffs%60;
    let zero =n=> n<10?'0'+n:n;
    return `${zero(h)}:${zero(m)}:${zero(s)}`;
}
function diffTime(futureDate){
    let futureTime = new Date(futureDate).valueOf();
    let time = new Date().valueOf();
    let diffs = (futureTime-time)/1000;
    return diffs;
}
function useDiffTime(futureDate){
    let [time,setTime] = useState(diffTime(futureDate));
    useEffect(()=>{
        setInterval(()=>{
            setTime(time=>time-1);
        },1000)
    },[])
    return time;
}
function Timer1(){
    let time= useDiffTime(new Date().valueOf()+4200000);; 
    return <div>
        倒计时{formate(time)}
    </div>
}
function Timer2(){
    let time= useDiffTime(new Date().valueOf()+3600000);; 
    return <div>
        倒计时{formate(time)}
    </div>
}
function Home(){
    return <div>
        <Timer1></Timer1>
        <Timer2></Timer2>
    </div>
}

render(<Home />,window.root);
复制代码

7.性能优化

  • 调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较算法 来比较 state。)简单不在给出事例。
  • 减少渲染次数。

1)把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

2)把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算

import React,{useState,useCallback,useMemo,memo} from 'react';
import {render} from 'react-dom';

function Child({onButtonClick,data}){
    console.log('Child render');
    return (
        <button onClick={onButtonClick} >{data.number}</button>
    )
}
let MemoChild = memo(Child);
function App(){
    const [number,setNumber] = useState(0);
    const [name,setName] = useState('zhufeng');
    const addClick = useCallback(()=>setNumber(number+1),[number]);
    const  data = useMemo(()=>({number}),[number]);
    console.log("App render");
    return (
      <div>
        <input type="text" value={name} onChange={e=>setName(e.target.value)}/>
        <MemoChild onButtonClick={addClick} data={data}/>
      </div>
    )
}

render(<App />,window.root);
复制代码

参考文献:

转载于:https://juejin.im/post/5d009c145188254cf36f0d1c

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值