React Hooks && 其它补充知识点

组件类的缺点

React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class)。
下面是一个简单的组件类。

import React, { Component } from "react";

export default class Button extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click me, please" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}

这个组件类仅仅是一个按钮,但可以看到,它的代码已经很"重"了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂。

Redux 的作者 总结了组件类的几个缺点。

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式

函数组件

React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。

React 早就支持函数组件,下面就是一个例子。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

但是,这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。

React Hooks

React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

Hook 这个单词的意思是"钩子"。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。

React 常用Hook

  • useState
  • useEffect
  • useRef
  • useContext
  • useReducer
  • useCallback
  • useMemo

useState

  • useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
//引入钩子函数
import {useState} from 'react';
const Test = () => {
    // useState的参数为状态初始值
    // useSibtate的返回值是一个数组
    // 数组第一个成员是变量,第二个成员是函数,用来更新状态,约定是set前缀加上状态的变量名
    const [num,setNum] = useState(1)
    return (
        <div>
            <div>访问num: {num}</div>
            <button onClick={()=>setNum(num+1)}>修改state</button>
        </div>
    );
}
export default Test

useEffect副作用钩子

useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。
useEffect() 可以实现生命周期函数的效果

import { useState, useEffect } from 'react';
const Test = () => {
    const [num, setNum] = useState(1)
    const [msg, setMsg] = useState('a')
    const [list, setList] = useState([])

    // 组件初始化自动执行一次,之后每更新一次执行一次
    // 类似 componentDidUpdate
    useEffect(()=>{
        console.log('没有第二个参数,多次执行');
    })

    //等价于 componentDidMount , 只在初始化时执行一次
    useEffect(()=>{
        console.log('第二个参数为空数组,只执行一次');
    },[])

    // 第二个参数为state, 初次执行后,只有该state改变时执行
    useEffect(()=>{
        console.log('第二个参数为state数据,数据改变一次执行一次');
    },[num])

    useEffect(()=>{
        //return的回调 等价于 componentWillUnmount() 组件卸载时
        return ()=>{
            console.log('组件卸载时');
        }
    },[])
    return (
        <div>
            <div>访问num: {num}</div>
            <button onClick={() => setNum(num + 1)}>修改num</button>
            <hr/>
            <div>访问msg: {msg}</div>
            <button onClick={() => setMsg(msg + 'm')}>修改msg</button>
        </div>
    );
}
export default Test

在父组件中实现组件卸载

import Test from './Test'
import { useState } from 'react';

const App = () => {
  const [flag,setFlag] = useState(true)
  return ( 
    <div>
      {/* 单击按钮,卸载组件 */}
      <button onClick={()=>{setFlag(false)}}>删除Test</button>
      { flag ? <Test></Test> : null}
    </div>
   );
}

useRef

useRef用来获取DOM元素

import { useRef , useEffect} from "react";

const Test = () => {
    //1. 创建引用实例
    const btnRef = useRef()
    useEffect(()=>{
        //3. 通过实例的current属性即可访问DOM对象
        btnRef.current.focus()
    },[])
    return (
        <div>
            {/* 2. 给DOM元素添加ref属性,值为useRef创建的实例 */}
            <input type="text"  ref={btnRef}/>
        </div>
    );
}
export default Test

useContext 共享状态钩子

如果需要在组件之间共享状态,可以使用useContext()

  1. 使用 React Context API,在组件外部建立一个 Context
    Context因为需要其它组件共享,所以要单独导出
export const AppContext = createContext()   
  1. 利用父组件状态钩子,创建共享状态
const [city,setCity] = useState('郑州')   
  1. 提供了一个 Context 对象,这个对象可以被子组件共享

value值为共享状态,值为一个对象

<AppContext.Provider value={{city,setCity}}>
    Test---{city}
    <hr/>
    <Child></Child>
</AppContext.Provider>

4、在后代组件中引入Context,

import {AppContext} from './Test'
  1. useContext()钩子函数用来引入 Context 对象,从中获取所需要的状态
import React,{useContext} from 'react';
......
const {city,setCity} = useContext(AppContext)

完整案例

Parent.js

import { createContext, useState } from 'react'
import Child from './Child'
export const AppContext = createContext()
const Parent = () => {
    const [city, setCity] = useState('郑州')
    return (
        <AppContext.Provider value={{ city, setCity }}>
            Parent---{city}
            <hr />
            <Child></Child>
        </AppContext.Provider>
    );
}
export default Parent;

Child.js

import GrandSon from './GrandSon'
const Child = () => {
    return ( 
        <div>
            Child
            <hr/>
            <GrandSon></GrandSon>
        </div>
     );
} 
export default Child;

GrandSon.js

import React,{useContext} from 'react';
import {AppContext} from './Test'

const GrandSon = () => {
    const {city,setCity} = useContext(AppContext)
    return ( 
        <div>
            GrandSon-----{city}
            <div><button onClick={()=>{setCity('广州')}}>改变城市</button></div>
            <hr/>
        </div>
     );
}
export default GrandSon;

useReducer

  1. useReducer 是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer
  2. useReducer 是 useState的代替方案,用于 state 复杂变化
  3. useReducer 是单个组件状态管理,组件通讯还需要 props
  4. redux 是全局状态管理,多组件共享数据

useReducer 接受一个 reducer 函数作为参数,
reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,
而 dispatch 可以发布事件来更新 state 。

import { useReducer } from 'react'

// 定义reducter,不像redux传递type
const reducer = (state,action)=> {
    switch(action){
        case 'add':
             return state + 1;
         case 'sub':
             return state - 1;
         default:
             return state;
    }
 }
const Parent = () => {
    // count为变量即状态 , dispatch用来发布事件改变状态 
    // useReducer第二个参数为count的初始值
    const [count, dispatch] = useReducer(reducer,3)
    return (
        <div>
            <div>count------{count}</div>
            {/* 用dispatch发布事件 */}
            <div><button onClick={()=>{dispatch('add')}}>add</button></div>
            <div><button onClick={()=>{dispatch('sub')}}>sub</button></div>
        </div>
    );
}

export default Parent;

useMemo

函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。

useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。

useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
反例: 不相关的val改变,expensive也会执行

import {useState} from 'react';
 
export default function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }
 
    return <div>
        <h4>{count}-{val}-{expensive()}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

用useMemo解决

import {useState,useMemo} from 'react';
 
 
export default function WithMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');

    // useMemo第一个参数是回调, 第二个是依赖的变量
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);
 
    return <div>
        <h4>{count}-{expensive}</h4>
        {val}
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}

useCallback

useCallback跟useMemo比较类似,但它返回的是缓存的函数。

分析下面的案例:
每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

import React, { useState, useCallback } from 'react';
 
const set = new Set();
 
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        console.log(count);
    }, [count]);
    set.add(callback);
    return <div>
        <h4>{count}</h4>
        <h4>{set.size}</h4>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

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

export default function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    //观察子组件userEffect在val值改变时不触发
    // const callback = useCallback(() => {
    //     console.log('cb');
    //     return count;
    // }, [count]);

    //观察子组件userEffect在val值改变时触发
    const callback = () => {
        console.log('cb');
        return count;
    };

    return <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    console.log('child');
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
        console.log('useeffect');
        setCount(callback());
    }, [callback]);
    return <div>
        子------{count}
    </div>
}

React-router 常用Hook

  • useParams
  • useHistory
  • useLocation
import { useEffect } from 'react'
import { useParams,useLocation,useHistory } from 'react-router-dom'
const Star = () => {
    //获取路由信息中location数据
    console.log('useLocation',useLocation())
    //获取路由信息中history,调用history中的方法,可实现编程式导航
    console.log('useHistory',useHistory())

    //获取动态路由的参数
    let { id } = useParams()
    //组件初始化时,获取id,请求数据
    useEffect(() => {
        console.log('获取' + id + '对应的数据')
    }, [])

    return (
        <div>Star</div>
    );
}

export default Star;

WithRouter的用法

默认只有路由组件的props才能获取路由信息,普通组件如何要获取路由信息,实现编程式导航,可以用withRouter来实现路由的注入

import { withRouter } from 'react-router-dom'
const CartChild = (props) => {
    console.log('CartChild', props)
    return (
        <div>
            CartChild
            <hr />
            <button onClick={() => { }}>回到首页</button>
        </div>
    );
}

export default withRouter(CartChild);

React.createElement的用法

调用 React.createElement 返回的是js对象

import React, { Component } from 'react';
import ReactDOM from 'react-dom'


let obj = React.createElement(   
  "div",
  {
    className: "page",
  },
  React.createElement('header', null, " Hello, This is React "),
  React.createElement("h1", null, "Start to learn right now!"),
  "Right Reserve."
);

ReactDOM.render(obj, document.getElementById("root"));

props.children的用法

类似vue中的插槽

父组件

import CartChild from './CartChild'
const Cart = () => {
    return (
        <div>
            Cart
            <hr />
            <CartChild>
                <div>
                    <h1>标签中间的内容</h1>
                    <div>第二块内容</div>
                </div>
                    {/* <h1>标签中间的内容</h1> */}
            </CartChild>
        </div>
    );
}

export default Cart;

子组件

import { withRouter } from 'react-router-dom'
const CartChild = (props) => {
    console.log('CartChild', props)
    return (
        <div>
            CartChild
            <hr/>
            {props.children}
        </div>
    );
}

export default withRouter(CartChild);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容