React Hook使用

一、组件类

React的核心是组件, 在v16.8之前,组件的标准写法是类(class)。

以下为一个简单的组件类:

import React, { Component } from 'react'

export default class Click extends Component{
  constructor(props){  //如果要使用this.props就必须给super加参数:super(props)
    super(props);
    this.state={
      showText: 'Click  me'
    }
  }
  handleClick(){  //button点击事件的方法
    this.setState({
     showText: 'hello world' 
    })
  }
  render(){
    return{
      <div>
        <button onClick=onClick={this.handleClick.bind(this)} >{this.state.showText}</button>
      </div>
    }
  }
}

运行结果

image.png

Reduxde 的作者 Dan Abramov 总结了组件类的几个缺点:
.
1:大型组件很难拆分和重构, 也很难测试。
2:业务逻辑分散在组件的各个方法中, 导致重复逻辑或关联逻辑
3:组件类引入了复杂的编程模式, 比如render、props、高阶组件

名词解读:
1、 export default可用于导出常量、函数、文件、模块等。 在其他文件中用import将其导入使用。(一个文件只能有一个export default)
2、extends是继承某个类。继承之后可以使用父类的方法,也可以重写父类的方法
3、constructor()是构造函数。 通过new命令生成对象实例时自动调用该方法。(该方法是类中必须有的 ==》 如果没有用到constructor可以不写, React会默认添加一个空的constructor)
4、super()是继承。子类必须在constructor()中调用super()方法(原因:子类并没有自己的this对象, 它只能继承父级类的this对象,然后对其加工。super()就是将父类的this对象继承给子类, 没有super()子类将得不到this对象)
5、render()方法是 接受输入的数据并返回需要展示的内容

二、函数组件

函数组件相比于类组件要简单些。函数组件的定义方式有两种:普通函数、箭头函数

const Hello=(props)=>{
  return <div>Hello Word! {props.name}</div>
}
或
function Hello(props){
  return <div>Hello Word! {props.name}</div>
}

函数组件不能包含状态state, 也不支持生命周期方法。 必须是一个纯函数

三、Hook的含义

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

1.无需修改组件结构的情况下, 在组件之间复用状态逻辑
2.将组件中相互关联的部分拆分成更小的函数(比如订阅、请求数据)
3.在非class的情况下可以使用React更多的特性

Hook这个单词的意思就是钩子

组件尽量写成纯函数, 如果需要外部功能和副作用, 就用钩子把外部代码钩进来

以下是React常用的钩子:

React钩子.png

四、useState():状态钩子

useState()用于为函数组件引入状态(state), 纯函数是没有状态的,所以把状态放入钩子里面。

函数组件重写上面(一)的组件类:

import React, { useState } from 'react'

export default function Click(){
  const [showText, setShowText ] = useState('Click  me')
  function handleClick(){
     return  setShowText('hello world')
  }
  return (
     <button onClick={handleClick}>{showText}</button>
  )
}

const [showText, setClickText ] = useState('Click me') 第一个成员为变量(showText),第二成员个为函数是用来更新状态,约定命名方式为set+状态名(setShowText)。'Click me'为showText变量的默认值。

运行结果

image.png

1、state为一个对象时

state是一个对象时,不可部分更新属性。因为setState不会帮我们合并属性。

代码举例:setState修改用户的姓名,其他属性不会被合并

import React,{ useState } from 'react'

export default function ChangeName(){
  const [showText, setShowText] = useState({name:'guoguo', age:'18'}) 
  function handleClick(){
    return setShowText({ //setState不会帮我们合并属性
      name: 'saisai'
    })
  }
  return(
      <div>
        <div>姓名:{showText.name}</div>
        <div>年龄:{showText.age}</div>
        <button onClick = { handleClick } >change Name</button>
      </div>
    )
}

运行结果:

image.png

function handleClick(){
    return setShowText({
      ...showText,  // 拷贝之前的所有属性
      name: 'saisai' // 这里的name覆盖之前的name
    })
  }

修改useState后的运行结果:

image.png

五、useEffect(): 副作用钩子

useEffect()用来具有副作用的操作, 最常见的就是向服务器请求数据。以前放在componentDidMount()里面的代码,现在可以放在useEffect()中。useEffect在浏览器渲染完成后执行

useEffect()的用法如下:

useEffect(()=>{
  // 异步函数体
}, [dependencies])

上面方法中,useEffect()接受两个参数, 第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出Effect的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略, 这时组件每次渲染时就会执行useEffect()

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

export default function effectFun(){
  const nameLists = ['guoguo', 'saisai', 'lulu', 'meimei']
  const [name, setName] = useState('guoguo')
  function changeName(){
   console.log('changeName触发')
   return name
  }
  useEffect(()=>{
    console.log('name改变的时候触发')
  }, [name])
  return(
    <div>
      <p>{changeName()}</p> //调用方法
    <button onClick={()=>setName(nameLists[Math.random()*nameLists.length << 0])}>修改名称</button>
    </div>
  )
}

运行结果:

image.png

  • 点击按钮使用setName的时候, 产生DOM操作,刷新页面DOM的同时也触发了p标签中的changeName函数
  • 然后调用副作用触发了nameeffect

以上1、2为第一次渲染页面的时候运行changeName()、useEffect()方法打印出的结果。3、4为点击“修改名称”按钮后打印的结果。运行结果1、2说明:页面每次重新渲染时就会执行useEffect()。运行结果3、4说明:当调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数

官方文档 useEffect “副作用”函数

1、useLayoutEffect布局副作用

useEffect在浏览器渲染完成后执行
useLayoutEffect 在浏览器渲染前执行 (比useEffect先执行)

六、useContext(): 共享状态钩子

useContext()组件之间共享状态钩子

useContext()的使用方法请看下面的例子:

import React, { createContext, useContext } from 'react'

const Context = createContext({}) //在组件外建立一个Context对象, 这个对象可以被子组件共享
function Guoguo(){ 
  const { name } = useContext(Context) // 共享父级的参数
  return (
    <div>
      <div>guoguo</div>
      <div>{name}</div>
    </div>
  )
}
function Saisai(){
  const { name } = useContext(Context)  // 共享父级的参数
  return (
    <div>
      <div>saisai</div>
      <div>{name}</div>
    </div>
  )
}
export default function Family(){  // 父级组件
  return(
    <Context.Provider value={{   //提供给子组件使用的值
      name: 'happy'
    }}>
      <div>
        <Guoguo/> //函数组件
        <Saisai/>
      </div>
    </Context.Provider>
  )
}

运行结果

image.png

七、useReducer(): action钩子

useReducer()钩子用来引入Reducer功能

  • React本身不提供状态管理功能,通常需要使用外部库。这方面常用的库是ReduxMobx
  • Redux的核心概念是 组件发出action后与状态管理器通信。状态管理器接收到action后,使用Reducer函数算出新的状态,Reducer函数的形式是(state, action) => newState

useReducer的用法举例:

import React, { useReducer } from 'react'

const myReducer = (state, action) => {  // useReducer不会对属性进行合并
  if(action.type === '0') {
    return{
      ...state, // 拷贝所有的状态属性
      count: state.count + 1 // 覆盖属性中的count字段 
    }
  } else {
    return state
  }
}
export default function reducerFun(){
  const [state, dispatch] = useReducer(myReducer, {count: 0})
  return (
    <div>
      <div>{state.count}</div>
      <button onClick={()=>dispatch({type: '0'})}>+1</button>
    </div>
  )
}

运行结果:

image.png

解读:

  1. useReducer ()它接Reducer函数和状态的初始值作为参数, 返回一个数组。数组的第一个成员是当前的状态值, 第二个成员是发送actiondispatch函数。
  2. 以上 myReducer() 接收状态值dispatch函数要发送的action作为参数, 返回一个新状态state
  3. useReducer与useState类似, 都不会合并属性

1、Redux中的名词解读:

  1. store:保存数据的地方,可以把它看成一个容器,整个应用只能有一个Store。
  2. state:store对象中包含所有的数据,如果想得到某个时点的数据,就要对store生成快照。这种时点的数据集合就叫state 注:redux规定一个state对应一个view,只要state相同,View就相同
  3. action:view视图发出的通知,表示state应该要发生变化(view要发送多少种通知就会有多少种action)
  4. action Creator:用来生成action的函数
  5. store.dispatch(): view发出action的唯一方法
  6. reducer:store收到action以后,必须给出一个新的state,这样view才会发生变化,这种state的计算过程就叫做reducer
  7. store.subscribe():设置监听函数,一旦state发生变化,就自动执行这个函数
    image.png

八、useMemo()

传入 useMemo的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是useMemo

useMemo() 用法举例:

import React, { useEffect, useState, useMemo } from 'react'

export default function effectFun() {
  const nameLists = ['guoguo', 'saisai', 'lulu', 'meimei']
  const [name, setName] = useState('guoguo')
  const [age, setAge] = useState(18)

  function changeName() {
    console.log('changeName触发')
    return name
  }
  useEffect(() => {
    console.log('name effect改变的时候触发')
  }, [name])

  const memoFun = useMemo(()=>{
    console.log('name memo改变的时候触发')
    return () => name // 返回一个函数
  }, [name])
  return (
    <div>
      <div>普通方法{changeName()}</div>
      <div>memo方法{memoFun()}</div>
      <button onClick={() => setName(nameLists[Math.random() * nameLists.length << 0])}>修改名称</button>
      <button onClick={() => setAge(age + 1)}>修改年龄</button>
    </div>
  )
}

运行结果:

image.png

  • 运行结果1:渲染页面的打印结果 useMemo是在页面渲染期间执行
  • 运行结果2:点击“修改名称”按钮的打印结果
  • 运行结果3:点击“修改年龄”按钮的打印结果

将以上代码中的memoFun()修改如下:

 const memoFun = useMemo(()=>{
    setName({
      name: name[Math.random() * nameLists.length << 0]
    })
    console.log('name memo改变的时候触发')
    return () => name
  }, [name])

运行结果:

image.png

  1. useMemoDOM改变的时候,可以控制某些函数不被触发点击“修改年龄”按钮时memoFun()没有执行
  2. useMemo中不可使用setState, 不然会造成死循环并有警告。useMemo是在渲染中进行的,如果在其中操作DOM后,又会导致memo触发
  3. memo是在DOM更新前触发的,就像官方所说的,类比生命周期的shouldComponentUpdate
  4. useMemo运行后返回的是一个函数

1、useCallback

  • 函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。
  • useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
  function showName(name){
    console.log(name + '改变的时候触发')
    return () => name
  }
// ---------------------------------------------------------
  const memoFun = useMemo(()=>showName('memo'), [name])
    // 等价于
  const callBackFun = useCallback(showName('callBack'), [name])

八、useRef()

可以用来引入DOM对象或`普通对象

useRef用法如下:

const refContainer = useRef(initialValue)

命令式地访问子组件:

  • useRef()返回一个可变的ref, 其.current属性被初始化为传入的参数(initialValue)。返回的ref对象在组件的整个生命周期保持不变
import React, { useRef } from 'react'

export default function getFoucsInput(){
  const inputRef = useRef(null)
  const buttonFocus = ()=>{ //current已经挂载到DOM上的文本输入元素
    console.log(inputRef)
    inputRef.current.focus()
    inputRef.current.value = '123' // console.log(inputRef.current.value) 控制台输出为: 123
  }
  const showText = ()=>{
    console.log('current变化时,是否渲染DOM')
  }
  return(
    <div>
      <div>{showText()}</div>
      <Input value='默认value'></Input>
      <button onClick={buttonFocus}>input获取焦点</button>
    </div>
  )
}

运行结果:

image.png

  1. ref对象内容发生变化时,useRef并不会通知你。
  2. 变更.current属性不会引发组件重新渲染 。点击按钮 “input获取焦点” 后 showText() 方法没有执行

1、props无法传递ref属性

import React, { useRef } from 'react'

export default function inputCom(){
  const inputRef = useRef(null)
  return(
    <div>
      <Input ref={inputRef} value='123'></Input>
    </div>
  )
}
const Input = (props, ref) => {
  console.log(props, ref) 
  return <input {...props} />
}

运行结果:

image.png

  • props 不包含 ref。 实现 ref 的传递需要用到forwardRef

2、forwardRef: 传递ref属性

export default function inputCom(){
  const inputRef = useRef({})
  return(
    <div>
      <Input ref={inputRef} value='123'></Input>
    </div>
  )
}
const Input = forwardRef((props, ref) => {
  console.log(props, ref)
  return <input ref={ref} {...props} />
})

运行结果:

image.png

九、自定义Hook

input输入框在change的时候输出input的值 以及 input的值+值形式例子如下:

import React, {  useState,  useRef } from 'react'

const useNumber = (name)=>{ //自定义Hook
  const setName = (newName)=>{
    if (newName){
      return `${newName} + ${newName}`
    }
  }
  return [name.n, setName] //返回input的值 以及 方法
}
const NumberCom = (n)=>{ //函数组件
  const [name, setName] = useNumber(n)
  return(
    <div>
      <div>输入框的值:{name}</div>
      <div>输入框的值+值:{setName(name)}</div>
    </div>
  )
}
export default function myHook(){
  const [inputVal, setInputVal] = useState()
  const inputRef = useRef(null)
  const changeName = () => {
    setInputVal(inputRef.current.value) //更新input的值
  }
  return (
    <div>
      <input ref={inputRef} onChange={changeName} placeholder='请输入...'></input>
      <NumberCom n={inputVal}></NumberCom>
    </div>
  )
}

运行结果:

image.png

  • useNumber() 为自定义的Hook。返回两个成员,第一个成员是input的值, 第二个成员是一个方法
  • NumberCom()是一个函数组件。参数n的结构为{n: "122"}

十、结束

仅供自己参考, 有问题请留言
巴拉拉小魔仙, 变身 ......

image.png

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