React相关扩展一(setState、lazyLoad、Hooks相关)(九)

时间:2023-01-10 19:56:54

系列文章目录

第一章:React基础知识(React基本使用、JSX语法、React模块化与组件化)(一)
第二章:React基础知识(组件实例三大核心属性state、props、refs)(二)
第三章:React基础知识(事件处理、受控组件与非受控组件、高阶函数、组件的生命周期)(三)
第四章:React脚手架应用(创建脚手架、代理配置、ajax相关、组件通信)(四)
第五章:react-router5路由相关一(路由相关概念、基本使用、NavLink与NavLink的封装、Switch的使用、严格匹配、路由重定向、路由组件与一般组件的区别)(五)
第六章:react-router5路由相关二(嵌套路由、路由传参、replace、编程式路由导航、withRouter的使用、BrowserRouter与HashRouter的区别)(六)
第七章:React-Router6路由相关一(路由的基本使用、重定向、NavLink·、路由表、嵌套路由)(七)
第八章:React-Router6路由相关二(路由传参、编程式路由导航、路由相关hooks)(八)
第九章:React相关扩展一(setState、lazyLoad、Hooks相关)(九)



一、 setState

1.1 setState更新状态的2种写法

  • setState(stateChange, [callback])------对象式的setState

    • stateChange状态改变对象(该对象可以体现出状态的更改)
    • callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
  • setState(updater, [callback])------函数式的setState

    • updater返回stateChange对象的函数,updater可以接收到state和props

    • callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用

代码案例:
Demo.jsx

import React, { Component } from 'react'

export default class Demo extends Component {

	state = { count: 0 }

	add = () => {
		//对象式的setState
		/* //1.获取原来的count值
		const {count} = this.state
		//2.更新状态
		this.setState({count:count+1},()=>{
			console.log(this.state.count);
		})
		//console.log('12行的输出',this.state.count); //0 */

		//函数式的setState
		this.setState((state, props) => ({ count: state.count + props.a }), () => {
			console.log('render后输出:', this.state.count);
		})
		console.log('render前输出:', this.state.count);
	}

	render () {
		return (
			<div>
				<h1>当前求和为:{this.state.count}</h1>
				<button onClick={this.add}>点我增加</button>
			</div>
		)
	}
}

App.js

import React, { Component, Fragment } from 'react'
import Demo from './components/1_setState'

export default class App extends Component {
	render () {
		return (
			<div>
				<Demo a={2} />
			</div>
		)
	}
}

运行结果:
React相关扩展一(setState、lazyLoad、Hooks相关)(九)

总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)

2.使用原则:

  • 如果新状态不依赖于原状态 ===> 使用对象方式
  • 如果新状态依赖于原状态 ===> 使用函数方式
  • 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取

二、 lazyLoad

2.1 路由组件的lazyLoad

使路由组件用时再加载

	//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
	const Login = lazy(()=>import('@/pages/Login'))
	
	//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
	<Suspense fallback={<h1>loading.....</h1>}>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
    </Suspense>

代码案例片段:

Demo.jsx

import React, { Component,lazy,Suspense} from 'react'
import {NavLink,Route} from 'react-router-dom'

// import Home from './Home'
// import About from './About'

import Loading from './Loading'
const Home = lazy(()=> import('./Home') )
const About = lazy(()=> import('./About'))

export default class Demo extends Component {
	render() {
		return (
			<div>
				<div className="row">
					<div className="col-xs-offset-2 col-xs-8">
						<div className="page-header"><h2>React Router Demo</h2></div>
					</div>
				</div>
				<div className="row">
					<div className="col-xs-2 col-xs-offset-2">
						<div className="list-group">
							{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
							<NavLink className="list-group-item" to="/about">About</NavLink>
							<NavLink className="list-group-item" to="/home">Home</NavLink>
						</div>
					</div>
					<div className="col-xs-6">
						<div className="panel">
							<div className="panel-body">
								<Suspense fallback={<Loading/>}>
									{/* 注册路由 */}
									<Route path="/about" component={About}/>
									<Route path="/home" component={Home}/>
								</Suspense>
							</div>
						</div>
					</div>
				</div>
			</div>
		)
	}
}

Loading.jsx

import React, { Component } from 'react'

export default class Loading extends Component {
	render() {
		return (
			<div>
				<h1 style={{backgroundColor:'gray',color:'orange'}}>Loading....</h1>
			</div>
		)
	}
}

Home.jsx

import React, { Component } from 'react'

export default class Home extends Component {
	render() {
		return (
			<h2>我是Home组件</h2>
		)
	}
}

About.jsx
同Home.jsx

运行结果:

React相关扩展一(setState、lazyLoad、Hooks相关)(九)


三、Hooks

3.1 React Hook/Hooks是什么?

(1) Hook是React 16.8.0版本增加的新特性/新语法
(2) 可以让你在函数组件中使用 state 以及其他的 React特性
(3) Hook是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用—— 这使得你不使用 class 也能使用 React。可以在新组件中慢慢使用Hook。

3.2 使用规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

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

3.3 三个常用的Hook

(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()

3.3.1 State Hook

(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:

  • 参数: 第一次初始化指定的值在内部作缓存
  • 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数

(4). setXxx()2种写法:

  • setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值

  • setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

代码案例:

函数式组件写法

function Demo () {
	//console.log('Demo');

	const [count, setCount] = React.useState(0)

	//加的回调
	function add () {
		//setCount(count+1) //第一种写法
		setCount(count => count + 1)
	}

	return (
		<div>
			<h2>当前求和为:{count}</h2>
			<button onClick={add}>点我+1</button>
		</div>
	)
}

export default Demo

类式组件写法

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

class Demo extends React.Component {

	state = {count:0}

	add = ()=>{
		this.setState(state => ({count:state.count+1}))
	}

	render() {
		return (
			<div>
				<h2>当前求和为{this.state.count}</h2>
				<button onClick={this.add}>点我+1</button>
			</div>
		)
	}
}

运行结果:
React相关扩展一(setState、lazyLoad、Hooks相关)(九)

3.3.2 Effect Hook

(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

(2). React中的副作用操作:

  • 发ajax请求数据获取

  • 设置订阅 / 启动定时器

  • 手动更改真实DOM

(3). 语法和说明:

  useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行

useEffect这个函数可以接收两个参数,第一个为函数;第二个为数组(可省略)。对于第二个参数, 如果你传入了一个空数组( [] ),effect 内部的 props 和 state 就会一直持有其初始值。可以向数组中添加需要监听的state,当该state发生变化会引起useEffect重新执行。useEffect 在 渲染时是异步执行,并且要等到浏览器将所有变化渲染到屏幕后才会被执行。跟 useState 一样,你可以在组件中多次使用 useEffect

(4). 可以把 useEffect Hook 看做如下三个函数的组合

  • componentDidMount()

  • componentDidUpdate()

  • componentWillUnmount()

代码片段案例:
函数式组件写法

function Demo () {
	const [count, setCount] = React.useState(0)

	React.useEffect(() => {
		console.log(count)
		let timer = setInterval(() => {
			add()
		}, 1000)
		return () => {
			clearInterval(timer)
		}
	}, [count])

	//加的回调
	function add () {
		//setCount(count+1) //第一种写法
		setCount(count => count + 1)
	}

	//卸载组件的回调
	function unmount () {
		ReactDOM.unmountComponentAtNode(document.getElementById('root'))
	}

	return (
		<div>
			<h2>当前求和为:{count}</h2>
			<button onClick={add}>点我+1</button>
			<button onClick={unmount}>卸载组件</button>
		</div>
	)
}

export default Demo

类式组件写法(运行结果同函数式组件的写法)

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

class Demo extends React.Component {

	state = {count:0}

	add = ()=>{
		this.setState(state => ({count:state.count+1}))
	}

	unmount = ()=>{
		ReactDOM.unmountComponentAtNode(document.getElementById('root'))
	}

	componentDidMount(){
		this.timer = setInterval(()=>{
			this.setState( state => ({count:state.count+1}))
		},1000)
	}

	componentWillUnmount(){
		clearInterval(this.timer)
	}

	render() {
		return (
			<div>
				<h2>当前求和为{this.state.count}</h2>
				<button onClick={this.add}>点我+1</button>
				<button onClick={this.unmount}>卸载组件</button>
			</div>
		)
	}
} 

运行结果:
React相关扩展一(setState、lazyLoad、Hooks相关)(九)

3.3.3 Ref Hook

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用: 保存标签对象,功能与React.createRef()一样,useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref对象在组件的整个生命周期内持续存在。

代码片段案例:

函数式组件写法

function Demo () {
	const myRef = React.useRef()

	// //提示输入的回调
	function show () {
		alert(myRef.current.value)
	}
	return (
		<div>
			<input type="text" ref={myRef} />
			<button onClick={show}>点我提示数据</button>
		</div>
	)
}

export default Demo

类式组件写法


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

//类式组件
class Demo extends React.Component {
	myRef = React.createRef()
	
	show = ()=>{
		alert(this.myRef.current.value)
	}
	render() {
		return (
			<div>
				<input type="text" ref={this.myRef}/>
				<button onClick={this.show}>点击提示数据</button>
			</div>
		)
	}
}

运行结果:

React相关扩展一(setState、lazyLoad、Hooks相关)(九)


3.4 其他Hook

3.4.1 useLayoutEffect(同步执行副作用)

  • useLayoutEffect其函数签名与useEffect相同,其调用时机和原来的componentDidMount、componentDidUpdate 一致,它会在所有的DOM变更之后同步调用effect。可以使用它来读取 DOM 布局并同步触发重渲染
  • 与useEffect的差异
  • 在浏览器执行绘制之前, useLayoutEffect内部的更新计划将被同步刷新,会阻塞页面渲染。而useEffect是会在整个页面渲染完才会调用的代码,是异步执行的,尽可能使用标准的useEffect以避免阻塞视觉更新。
  • useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和componentDidMount 等价。

在实际使用时如果想避免页面抖动(在useEffect 里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect 里。

案例对比效果不明显,暂无案例

3.4.2 useMemo(记忆组件)

useMemo(() => fn, deps)相当于useCallback(fn, deps)返回一个 memoized 值

  • 把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算
    memoized
    。这种优化有助于避免在每次渲染时都进行高开销的计算。
  • 传入useMemo的函数会在渲染期间执行。请不要在这个函数内部执行不应该在渲染期间内执行的操作,诸如副作用这类的操作属于 useEffect的适用范畴,而不是useMemo
  • 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

代码案例:

import React, { useState, useMemo } from "react";

function Demo() {
  const [num, setNum] = useState(0);
  const [result, setResult] = useState(0);
  let total = useMemo(() => {
    console.log("computed...", Math.random());
    setResult(result + num);
    return result;
  }, [num]);
  return (
    <div>
      <h2>计数器</h2>
      <div>上次结果 : {total}</div>
      <div>本次结果 : {result}</div>
      <div>num : {num}</div>
      <div>
        <button
          onClick={() => {
            setNum(num + 1);
          }}
        >
          change
        </button>
      </div>
    </div>
  );
}

export default Demo;

运行结果:
React相关扩展一(setState、lazyLoad、Hooks相关)(九)

与useEffect有点类似,只有useMemo中的第二个参数数组内变量发生变化的时候,才会去重新执行expensive方法。其功能类似vue中的计算属性。

3.4.3 useCallback(记忆函数)

防止因为组件重新渲染,导致方法被重新创建,起到缓存的作用,只有第二个参数变化了,才重新声明一次。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)返回一个 memoized 回调函数。

  • 把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized
    版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染。

(例如 shouldComponentUpdate)的子组件时,它将非常有用。

代码案例:

import React, { useState, useCallback } from "react";

function Demo() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  const memoizedCallback = useCallback(() => {
    // doSomething(a, b);
    console.log(a, b);
  }, [a, b]);
  // 只有a,b改变了才会重新声明该函数
  // 如果传入空数组,在第一次创建后被缓存,如果a,b改变了,获取的是旧值
  // 如果没有传第二个参数,那么每次都会重新声明该函数,拿的是新值
  return (
    <div>
      <button
        onClick={() => {
          memoizedCallback();
        }}
      >
        useCallback测试
      </button>
      <button
        onClick={() => {
          setA(100);
        }}
      >
        更改a的值
      </button>
      <button
        onClick={() => {
          setB(100);
        }}
      >
        更改b的值
      </button>
    </div>
  );
}

export default Demo;

运行结果:
React相关扩展一(setState、lazyLoad、Hooks相关)(九)