React学习笔记7:React使用注意事项

1. setState

setState更新状态的2种写法

方式一:setState(stateChange, [callback])------对象式的setState
  • stateChange为状态改变对象(该对象可以体现出状态的更改)
  • callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
方式二:setState(updater, [callback])------函数式的setState
  • updater为返回stateChange对象的函数
  • updater可以接收到state和props
  • callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结

对象式的setState是函数式的setState的简写方式(语法糖)

使用原则

以下原则只是建议性的,不是强制性的要求

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

setState同步VS异步问题

  • setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。

    • 合成事件:就是react 在组件中的onClick等都是属于它自定义的合成事件
    • 原生事件:比如通过addeventListener添加的,dom中的原生事件
  • setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

    在这里插入图片描述

  • 从上图可知,在react中,setState通过一个队列机制实现state的更新。当执行setState时,会把需要更新的state合并后放入状态队列,而不会立刻更新this.state,当进入组件可更新状态时,这个队列机制就会高效的批量的更新state
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);
		// })
		// //假如setState更新完数据后,立即使用的话,会出现问题
		// //console.log('获取最新的count数据为',this.state.count); //0 

		//方式二:函数式的setState
		this.setState(state => ({ count: state.count + 1 }))
	}

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

2. lazyLoad(懒加载组件)

路由组件的lazyLoad

  • 步骤一:通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包

    const Login = lazy(()=>import('@/pages/Login'))
    
  • 步骤二:通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面

    <Suspense fallback={<h1>loading.....</h1>}>
            <Switch>
                <Route path="/xxx" component={Xxxx}/>
                <Redirect to="/login"/>
            </Switch>
        </Suspense>
    

3. Hooks

1. React Hook/Hooks是什么?

  • Hook是React 16.8.0版本增加的新特性/新语法
  • 可以在函数组件中使用 state 以及其他的 React 特性

2. 三个常用的Hook

  • State Hook: React.useState()
  • Effect Hook: React.useEffect()
  • Ref Hook: React.useRef()
2.1. State Hook
  • State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
  • 语法: const [xxx, setXxx] = React.useState(initValue)
  • useState()说明
    • 参数: 第一次初始化指定的值在内部作缓存
    • 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
  • setXxx()两种写法
    • setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
    • setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
2.2. Effect Hook
  • Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

  • React中的副作用操作

    1. 发ajax请求数据获取
    2. 设置订阅 / 启动定时器
    3. 手动更改真实DOM
  • 语法和说明

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

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

    • componentDidMount()
    • componentDidUpdate()
    • componentWillUnmount()

5. Ref Hook
  • Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据

  • 语法

    const refContainer = useRef()
    
  • 作用

    保存标签对象,功能与React.createRef()一样

4. Fragment(避免额外标记)

  • 概述

    • 使用 Fragment 减少了包含的额外标记数量,这些标记只是为了满足在 React 组件中具有公共父级的要求。
    • 在创建新组件时,每个组件应具有单个父标签。父级不能有两个标签,所以顶部要有一个公共标签。所以经常在组件顶部添加额外标签
    • 以将元素包含在片段(fragement)中。片段不会向组件引入任何额外标记,但它仍然为两个相邻标记提供父级,因此满足在组件顶级具有单个父级的条件
  • 使用方式

    <Fragment><Fragment>
    <></>
    
  • 作用

    可以不用必须有一个真实的DOM根标签了

  • <Fragment><>区别

    • <></> 语法不能接受键值或属性,<Fragment>可以
    • 使用显式 <Fragment>语法声明的片段可能具有 key。key 是唯一可以传递给 Fragment 的属性
import React, { Component, Fragment } from 'react'

export default class Demo extends Component {
	render() {
		return (
			<Fragment key={1}>
				<input type="text" />
				<input type="text" />
			</Fragment>
		)
	}
}

5. Context

使用Context的原因

  • 为了有时候想传递数据通过组件树,但是不想给每一层级的组件手动传递属性,那么 context就能实现跨级传递数据到组件树中你想传递到的深层次组件

  • 有时候 A组件 为了给 B组件 中的 C组件 传递一个 prop ,而需要把参数在组件中传递两次才能最终将 A组件 中的 prop 传递给 C组件

  • 一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用方式

  1. 创建Context容器对象

    const XxxContext = React.createContext()  
    
  2. 渲染子组件时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据

    <xxxContext.Provider value={数据}>
    		子组件
    </xxxContext.Provider>
    
  3. 后代组件读取数据

    //第一种方式:仅适用于类组件 
    static contextType = xxxContext  // 声明接收context
    this.context // 读取context中的value数据
    	  
    //第二种方式: 函数组件与类组件都可以
    <xxxContext.Consumer>
         {
              value => ( // value就是context中的value数据
              要显示的内容
              )
    	}
    </xxxContext.Consumer>
    

示例

.parent{
	width: 500px;
	background-color: orange;
	padding: 8px;
}
.child{
	width: 100%;
	background-color: skyblue;
	padding: 8px;
}
.grand{
	width: 100%;
	background-color: gray;
	padding: 8px;
}
import React, { Component, createContext, useContext } from 'react'
import './index.css'

//创建Context对象
const MyContext = createContext()
const { Provider, Consumer } = MyContext
export default class A extends Component {

	state = { username: 'Jack', age: 18 }

	render() {
		const { username, age } = this.state
		return (
			<div className="parent">
				<h3>我是A组件</h3>
				<h4>我的用户名是:{username}</h4>
				<Provider value={{ username, age }}>
					<B />
				</Provider>
			</div>
		)
	}
}

class B extends Component {
	render() {
		return (
			<div className="child">
				<h3>我是B组件</h3>
				<C />
			</div>
		)
	}
}

// 方式一
/* class C extends Component {
	//声明接收context
	static contextType = MyContext
	render() {
		const {username,age} = this.context
		return (
			<div className="grand">
				<h3>我是C组件</h3>
				<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
			</div>
		)
	}
} */

//方式二
/* function C() {
	const contextData = useContext(MyContext)
	const { username, age } = contextData
	return (
		<div className="grand">
			<h3>我是C组件</h3>
			<h4>我从A组件接收到的用户名:{username},年龄是=={age}</h4>
		</div>
	)
} */

//方式三
function C() {
	return (
		<div className="grand">
			<h3>我是C组件</h3>
			<h4>我从A组件接收到的用户名:
			<Consumer>
					{value => `${value.username},年龄是${value.age}`}
				</Consumer>
			</h4>
		</div>
	)
}

6. 组件优化

概述

  • 浏览器的重绘和重排版(reflows & repaints)(DOM操作都会引起)才是导致网页性能问题的关键。而React虚拟DOM的目的就是为了减少浏览器的重绘和重排版
  • 组件渲染方式有两种初始渲染更新渲染,而我们需要优化的地方就是更新渲染
  • 组件更新生命周期中必调用shouldComponentUpdate(组件是否应该更新)。shouldComponentUpdate默认返回true,必更新。所有当我们判断出组件没必要更新是,shouldComponentUpdate可以返回false,就达到优化效果

Component的2个问题

  • 只要执行setState(),即使不改变状态数据,组件也会重新render()==> 效率低
  • 只当前组件重新render(),就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

  • 只有当组件的state或props数据发生改变时才重新render()

原因

  • Component中的shouldComponentUpdate()总是返回trueshouldComponentUpdate是决定react组件什么时候能够不重新渲染的函数,但是这个函数默认的实现方式就是简单的返回一个true。也就是说,默认每次更新的时候都要调用所用的生命周期函数,包括render函数,重新渲染。

解决

方式一
  • 重写shouldComponentUpdate()方法
  • 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
方式二
  • 使用PureComponent
  • PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
  • 注意事项
    • 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
    • 不要直接修改state数据, 而是要产生新数据
  • 项目中一般使用PureComponent来优化
示例
  • 样式

    .parent{
    	background-color: orange;
    	padding: 10px;
    }
    .child{
    	background-color: gray;
    	margin-top: 30px;
    	padding: 10px;
    }
    
  • js代码

    import React, { PureComponent } from 'react'
    import './index.css'
    
    export default class Parent extends PureComponent {
    
    	state = {
    		carName: "奔驰c36",
    		stus: ['小张', '小李', '小王']
    	}
    
    	addStu = () => {
    		const { stus } = this.state
    		this.setState({ stus: ['小刘', ...stus] })
    	}
    
    	changeCar = () => {
    		//this.setState({carName:'迈巴赫'})
    
    		const obj = this.state
    		obj.carName = '迈巴赫'
    		console.log(obj === this.state);
    		this.setState(obj)
    	}
    
    	/* shouldComponentUpdate(nextProps,nextState){
    		console.log("目前的props和state===",this.props,this.state); //目前的props和state
    		console.log("接下要变化的目标props,目标state===",nextProps,nextState); //接下要变化的目标props,目标state
    		return !this.state.carName === nextState.carName
    	} */
    
    	render() {
    		console.log('Parent---render');
    		const { carName,stus } = this.state
    		return (
    			<div className="parent">
    				<h3>我是Parent组件</h3>
    				{stus}&nbsp;
    				<span>我的车名字是:{carName}</span><br />
    				<button onClick={this.changeCar}>点我换车</button>
    				<button onClick={this.addStu}>添加一个小刘</button>
    				<Child carName="奥拓" />
    			</div>
    		)
    	}
    }
    
    class Child extends PureComponent {
    
    	/* shouldComponentUpdate(nextProps,nextState){
    		console.log(this.props,this.state); //目前的props和state
    		console.log(nextProps,nextState); //接下要变化的目标props,目标state
    		return !this.props.carName === nextProps.carName
    	} */
    
    	render() {
    		console.log('Child---render');
    		return (
    			<div className="child">
    				<h3>我是Child组件</h3>
    				<span>我接到的车是:{this.props.carName}</span>
    			</div>
    		)
    	}
    }
    

7. render props

如何向组件内部动态传入带内容的结构(标签)?

  • Vue中,使用slot插槽技术,也就是通过组件标签体传入结构 <A><B/></A>
  • React中,可以通过以下两种方式
    • 使用children props:通过组件标签体传入结构
    • 使用render props:通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}

问题: 如果B组件需要A组件内的数据, ==> 以上方式是无法做到的

render props

<A render={(data) => <C data={data}></C>}></A>
  • A组件: {this.props.render(内部state数据)}
  • C组件: 读取A组件传入的数据显示 {this.props.data}

8. 错误边界

概述

  • 在 UI 部分发生的 JavaScript 异常不应该阻断整个应用。为了解决这一问题,React 16 引入了“错误边界(error boundary)”这一新概念。

  • 错误边界作为 React 组件,用以捕获在子组件树中任何地方的 JavaScript 异常,打印这些错误,并展示备用 UI 而非让组件树崩溃。错误边界会捕获渲染期间,在生命周期方法中以及在其整个树的构造函数中的异常。

  • 错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

  • 错误边界仅可以捕获组件树中的后代组件错误。一个错误边界无法捕获其自身的错误。若错误边界在渲染错误信息时失败,则该错误会传递至上一层最接近的错误边界

特点

  • 只能捕获后代组件生命周期产生的错误
  • 不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

示例

  • 子组件代码

    import React, { Component } from 'react'
    
    export default class Child extends Component {
    	state = {
    		users: [
    			{ id: '001', name: 'tom', age: 18 },
    			{ id: '002', name: 'jack', age: 19 },
    			{ id: '003', name: 'peiqi', age: 20 },
    		]
    		//错误边界演示数据
    		// users:'abc'
    	}
    
    	render() {
    		return (
    			<div>
    				<h2>我是Child组件</h2>
    				{
    					this.state.users.map((userObj) => {
    						return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>
    					})
    				}
    			</div>
    		)
    	}
    }
    
  • 父组件代码

    import React, { Component } from 'react'
    import Child from './Child'
    
    export default class Parent extends Component {
    
    	state = {
    		hasError: '' //用于标识子组件是否产生错误
    	}
    
    	//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
    	static getDerivedStateFromError(error) {
    		console.log('@@@', error);
    		return { hasError: error }
    	}
    
    	// React 16 将提供一个内置函数 componentDidCatch,如果 render() 函数抛出错误,则会触发该函数
    	// componentDidCatch 是包含错误堆栈的 info 对象
    	componentDidCatch(error, info) {
    		console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
    		console.log('object error:>> ', error);
    		console.log('object info:>> ', info);
    		console.log('错误堆栈:>> ', info.componentStack);
    	}
    
    	render() {
    		return (
    			<div>
    				<h2>我是Parent组件</h2>
    				{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
    			</div>
    		)
    	}
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值