小白入门学习 react 新特性 react-hooks

2018年底 FaceBook 的 React 小组推出 Hooks。React Hooks就是用函数的形式代替原来的继承类的形式,并且使用预函数的形式管理state,有Hooks可以不再使用类的形式定义组件了。原来把组件分为有状态组件和无状态组件,有状态组件用类的形式声明,无状态组件用函数的形式声明。现在所有的组件都可以用函数来声明了。

创建一个项目进行学习演示

mkdir ReactHooksDemo
cd ReactHooksDemo
create-react-app demo

npm start 之后,开启项目,开始 react-hooks 的学习

编写之前,将 src/index.js 进行删减

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  document.getElementById('root')
);

1. react-hooks 与之前的 react 有什么不同,可以编写相同的功能,进行比对

(写一个点击按钮,数量不断增加)

新建一个 Example.js

import React, { Component } from 'react';

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }
  }
  render() {
    return (
      <div>
        <p>点击了 {this.state.count}</p>
        <button onClick={this.addCount.bind(this)}>点击</button>
      </div>
    );
  }
  addCount() {
    this.setState({ count: this.state.count + 1 })
  }
}
export default Example;

react-hooks

import React, { useState } from 'react'

function Example() {
  const [count, setCount] = useState(0)
  
  return (
    <div>
      <p>点击了 {count}</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  )
}

export default Example

不说其他的,代码量很明显就少了很多。

hooks的目的就是让你不再写class,让function一统江湖。

2. useState 是 react 自带的一个 hook 函数,它的作用是用来声明状态变量。

声明的方式

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

useState 函数接收的参数是状态的初始值,返回一个数组,这个数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。上面的代码的意思是声明了一个状态变量为 count,并把它的初始值设为0,同时提供了一个可以改变count的状态值的方法函数。

声明一个状态之后,接下来就需要读取状态中的值。

<p>点击了 {count} 次</p>

是不是超级简单

接下来改变 State 中的值

<button onClick={() => setCount(count + 1)}>点击</button>

之前我们说过 useState 返回一个数组,数组的第一位就是改变状态值的方法函数。直接调用setCount函数,这个函数接收的参数是修改过的新状态值。

声明多个状态值
import React, { useState } from 'react'

function Example() {
  const [react, setReact] = useState('react')
  const [vue, setVue] = useState('vue')
  const [angular, setAngular] = useState('angular')
  
  return (
    <div>
      <p>{react}</p>
      <p>{vue}</p>
      <p>{angular}</p>
    </div>
  )
}
export default Example
React Hooks不能出现在条件判断语句中,因为它必须有完全一样的渲染顺序。

3. Class 制作组件时,经常会用生命周期函数,来处理一些额外的事情(副作用:和函数业务主逻辑关联不大,特定时间或事件中执行的动作,比如Ajax请求后端数据,添加登录监听和取消登录,手动修改DOM等等。在 React Hooks 中也需要这样类似的生命周期函数,比如在每次状态(State)更新时执行,它为我们准备了 useEffect

原先的写法
(在页面渲染之后会控制台输出 componentDidMount 0)
(之后每一次点击,改变 count 的值,控制台就会输出 componentDidUpdate 1, 2, 3…)

import React, { Component } from 'react';

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }
  }

  componentDidMount() {
    console.log(`componentDidMount ${this.state.count}`)
  }

  componentDidUpdate() {
    console.log(`componentDidUpdate ${this.state.count}`)
  }

  render() {
    return (
      <div>
        <p>点击了 {this.state.count}</p>
        <button onClick={this.addCount.bind(this)}>点击</button>
      </div>
    );
  }
  addCount() {
    this.setState({ 
      count: this.state.count + 1 
    })
  }
}
export default Example;

react-hooks

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

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

  useEffect(() => {
    console.log(`useEffect ${count}`)
  })
  
  return (
    <div>
      <p>点击了 {count}</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  )
}

export default Example

一行代码即可解决。
(在页面渲染之后会控制台输出 useEffect 0)
(之后每一次点击,改变 count 的值,控制台就会输出 useEffect 1, 2, 3…)

首先,声明了一个状态变量 count ,它的初始值设为 0,然后告诉 react,我们的这个组件有一个副作用。给 useEffect 传一个匿名函数,这个匿名函数就是我们的副作用,打印了一句话。当 React 要渲染组件时,它会记住用到的副作用,然后执行一次。等 React 更新了 State 状态时,它再一次执行定义的副作用函数。

使用 useEffect 的注意点

React 首次渲染和之后的每次渲染都会调用一遍 useEffect 函数 (从我们刚刚控制台的输出情况可以看出) ,而之前我们要用两个生命周期函数分别表示首次渲染 (componentDidMonut) 和更新导致的重新渲染 (componentDidUpdate) 。
useEffect 中定义的函数,执行时不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonutcomponentDidUpdate中的代码都是同步执行的。


4. useEffect 实现 componentWillUnmount生命周期函数(组件将要被卸载时执行)定时器要清空,避免发生内存泄漏;比如登录状态要取消掉,避免下次进入信息出错,解绑副作用

为了演示用 useEffect 来实现类似 componentWillUnmount 效果,安装 React-Router

npm install --save react-router-dom

src / index.js

import { BrowserRouter as Router, Route, Link } from "react-router-dom"

声明两个简单的 hooks

function Index() {
  return (
    <h2>index pages</h2>
  )
}

function List() {
  return (
    <h2>list pages</h2>
  )
}

增加路由

return (
  <div>
    <p>点击了 {count}</p>
    <button onClick={() => setCount(count + 1)}>点击</button>
    <Router>
      <ul>
        <li>
          <Link to="/">index - 首页</Link>
        </li>
        <li>
          <Link to="/list/">list - 列表</Link>
        </li>
      </ul>
      <Route path="/" exact component={Index}></Route>
      <Route path="/list/" exact component={List}></Route>
    </Router>
  </div>
)

确认页面渲染无误后,我们给两个新增的 hooks 函数增加 useEffect

function Index() {
  useEffect(() => {
    console.log('useEffect index 页面 进入')
  })
  return (
    <h2>index pages</h2>
  )
}

function List() {
  useEffect(() => {
    console.log('useEffect list 页面 进入')
  })
  return (
    <h2>list pages</h2>
  )
}

这时候我们点击Link进入任何一个组件,在浏览器中都会打印出对应的一段话。此时可以用返回一个函数的形式进行解绑

function Index() {
  useEffect(() => {
    console.log('useEffect index 页面 进入')
    return () => {
      console.log('index 页面 卸载了')
    }
  })
  return (
    <h2>index pages</h2>
  )
}

当你离开 index 页面时,控制台就会打印出 index 页面卸载。其实每次状态发生变化,useEffect都进行了解绑。

实现类似 componentWillUnmount 的效果,需要请出 useEffect 的第二个参数,它是一个数组,数组中可以写入很多状态对应的变量,意思是当状态值发生变化时,才进行解绑。但是当传空数组 [] 时,就是当组件将被销毁时才进行解绑,这也就实现了 componentWillUnmount 的生命周期函数。

function Index() {
  useEffect(() => {
    console.log('useEffect index 页面 进入')
    return () => {
      console.log('index 页面 卸载了')
    }
  }, [])
  return (
    <h2>index pages</h2>
  )
}

我们给计数组件加上 useEffect 解绑作用

useEffect(() => {
  console.log(`useEffect=>点击了 ${count} 次`)
  return () => {
    console.log('走了走了')
  }
}, [count])

这时候只要 count 状态发生变化,都会执行解绑副作用函数,浏览器的控制台也就打印出了一串‘ 走了走了’。


5. useContext 让父子组件传值更简单

在用类声明组件时,父子组件的传值是通过组件属性和 props 进行的,现在使用方法 (Function) 来声明组件,已经没有了 constructor 构造函数也就没有了props的接收,那父子组件的传值就成了一个问题。React Hooks 为我们准备了useContextuseContext,可以帮助我们跨越组件层级直接传递变量,实现共享。

想要使用就要创建它,使用 createContext 创建 context
import React, { useState, createContext } from 'react'

const CountContext = createContext()

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

  return (
    <div>
      <p>点击了 {count}</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
      <CountContext.Provider value={count}>
      </CountContext.Provider>
    </div>
  )
}

export default Example

count 变量允许跨层级实现传递和使用了(也就是实现了上下文),当父组件的 count变量发生变化时,子组件也会发生变化。使用 createContext 创建了一个名为 CountContext 的上下文环境,想要把 count 这个值 传入给子组件,子组件要放在

<CountContext.Provider value={count}>
</CountContext.Provider>

环境之中,才可以接收到传入的值

使用 useContext 来接收

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

function Counter(){
  const count = useContext(CountContext)
  return (
    <h2>{count}</h2>
  )
}
<CountContext.Provider value={count}>
  <Counter></Counter>
</CountContext.Provider>

点击按钮的时候,子组件 Counter,就可以接收到了。


6. useReducer介绍和简单使用。reducer其实就是一个函数,这个函数接收两个参数,一个是状态,一个用来控制业务逻辑的判断参数。

之前我们计数器只有加,现在我们来个减法

function countReducer(state, action) {
  switch(action.type) {
    case 'add':
      return state + 1
    case 'sub':
      return state - 1
    default:
      return state
  }
}

上面的代码就是Reducer,你主要理解的就是这种形式和两个参数的作用,一个参数是状态,一个参数是如何控制状态。

useReducer,是React hooks提供的函数,可以增强我们的Reducer,实现类似 Redux 的功能,用useReducer实现计数器的加减双向操作

import React, { useReducer } from 'react'


function ReducerDemo() {
  const [count, dispatch] = useReducer((state, action) => {
    switch(action) {
      case 'add':
        return state + 1
      case 'sub':
        return state - 1
      default:
        return state
    }
  }, 0)

  return (
    <div>
      <p>点击了 {count}</p>
      <button onClick={() => dispatch('add')}>加一</button>
      <button onClick={() => dispatch('sub')}>减一</button>
    </div>
  )
}

export default ReducerDemo

7. useMemo 优化 React Hooks 程序性能

useMemo 主要用来解决使用React hooks产生的无用渲染的性能问题。使用 function 的形式来声明组件,失去了shouldCompnentUpdate(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分 mount 和 update 两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemouseCallback 都是解决上述性能问题的,

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

function ReducerDemo() {
  const [vue, setVue] = useState('vue 来了')
  const [react, setReact] = useState('react 来了')

  return (
    <div>
      <button onClick={() => setVue(new Date().getTime())}>{vue}</button>
      <button onClick={() => setReact(new Date().getTime())}>{react}</button>
      <ChildComponent name={vue}>{vue}</ChildComponent>
    </div>
  )
}

function ChildComponent({ name, children }) {
  function changeVue(name) {
    console.log('vue 来了·······')
    return name + 'vue 真的来了'
  }
  const actionVue = changeVue(name)
  return (
    <>
      <div>{actionVue}</div>
      <div>{children}</div>
    </>
  )
}
export default ReducerDemo

在浏览器中点击 react 按钮,vue 对应的方法都会执行,结果虽然没变,但是每次都执行,这就是性能的损耗。
而我们要实现的是,只有点击 vue 的时候,才会改变 vue

const actionVue = useMemo(() => {
  changeVue(name)
}, [name])

浏览器中点击 react 按钮,changeVue 就不再执行了。也节省了性能的消耗。


8. useRef获取DOM元素
import React, {useState, useMemo, useRef } from 'react'


function ReducerDemo() {
  const input = useRef(null)

  const getInputRef = () => {
    input.current.value = 'hello react-hooks'
    console.log(input)
  }

  return (
    <div>
      <input ref={input} type="text" />
      <button onClick={getInputRef}>获取 ref 值</button>
    </div>
  )
}

export default ReducerDemo

点击按钮时,可以看到在浏览器中的控制台完整的打印出了 DOM 的所有东西,并且界面上的<input/> 框的 value 值也输出了我们写好的 ‘hello react-hooks’ 。说明我们可以使用 useRef 获取DOM元素,并且可以通过 useRef 控制DOM的属性和值。


9.自定义 hooks 函数

其实自定义 Hooks 函数和用 Hooks 创建组件很相似,我们平时用 JavaScript 写函数几乎一模一样,就是多 React Hooks的特性,自定义Hooks函数偏向于功能,而组件偏向于界面和业务逻辑。自定义 Hooks 函数,记住一定要用use开头,这样才能区分出什么是组件,什么是自定义函数。

获取浏览器窗口大小

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

function useWinSize() {
  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)
    return () => {
      window.removeEventListener('resize', onResize)
    }
  }, [])

  return size;
}

使用

function Example() {
  const size = useWinSize()
  return (
    <div>
      size: {size.width} × {size.height}
    </div>
  )
}

参考文章:https://jspang.com/detailed?id=50#toc212

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值