react扩展,setState、lazyLoad、hooks、Fragment、Context、pureComponent、renderProps、ErrorBoundary
-
一、setState的两种用法:
setState是异步的调用,当我们代用过setState后,紧接着打印state的值,发现是更改之前的值。
对象式和函数式
import React, { Component } from 'react'
export default class Demo extends Component {
state = { count: 0 };
add = () => {
// 对象式的setState
// // setState是异步的
// // 第二个可选参数是个回调函数,是在值更新后,render调用后再调用
// this.setState({count: this.state.count+1}, () => {
// console.log(this.state.count, '值更改之后'); // 1
// });
// console.log(this.state.count, 'this.state.count=='); // 0
// 函数式的setState
// 函数的参数可以接受到state和props
this.setState((state, props) => {
return { count: state.count+1}
})
// 对象式的setState是函数式的简写方式(语法糖)
// 如果依赖于原来的状态,推荐用函数式的
}
render() {
return (
<div>
<h2>当前求和为:{this.state.count}</h2>
<button onClick={this.add}>点我加1</button>
</div>
)
}
}
二、路由的lazyLoad
懒加载
若果不用懒加载的形式,当项目加载时,会把所有的组件一下加载下来,会导致首次加载的资源很多,首页出现白屏的时间很长
懒加载是调用react中的lazy方法实现路由来加载的,还会用到react中自带的一个组件Suspense,这个组件用来指定组件未加载的时候显示的内容,可以是node节点,可以是组件,不过这个组件不能是懒加载。
import React, { lazy, Suspense } from 'react'
import { NavLink, Route, Routes } from 'react-router-dom'
// import Home from '../Home'
// import About from '../About'
// 懒加载
const Home = lazy(() => import('../Home'))
const About = lazy(() => import('../About'))
export default class Demo extends React.Component {
render() {
return (
<div className="container">
<div className="main">
<aside className="aside">
{ /**
* 编写路由链接
* 在react中,靠路由连接切换组件
*/}
<NavLink className="btn" to="/home">home</NavLink>
<NavLink className="btn" to="/about">about</NavLink>
</aside>
<div className="content">
{/**
* 注册路由
*/}
{/* Suspense组件是指定在路由对应的组件未加载之前显示的内容,可以是组件 */}
<Suspense fallback={<h1>路由为加载之前</h1>}>
<Routes>
<Route path="/home" element={<Home/>}></Route>
<Route path="/about" element={<About/>}></Route>
</Routes>
</Suspense>
</div>
</div>
</div>
)
}
}
三、hooks
1、hooks是react16.8版本增加的新特性
2、它可以让你在函数组件中使用state以及其他特性
三个常用的hook
useState、useEffect、useRef
import React from 'react' // 类式组件 // class Demo extends React.Component { // state = { count: 0 } // myRef = React.createRef() // add = () => { // this.setState({count: this.state.count+1}) // } // componentDidMount() { // setInterval(() => { // this.setState(state => ({count: state.count+1})) // }, 1000) // } // unmount = () => { // const { root } = this.props; // root.unmount(); // 18版本 // // ReactDOM.unmountComponentAtNode(document.getElementById('root')) // } // componentWillUnmount(){ // console.log('组件即将卸载'); // } // show = () => { // alert(this.myRef.current.value) // } // render() { // return ( // <div> // <input ref={this.myRef} type="text" /> // <h1>当前求和为:{this.state.count}</h1> // <button onClick={this.add}>加1</button> // <button onClick={this.unmount}>卸载组件</button> // <button onClick={this.show}>点击提示数据</button> // </div> // ) // } // } // 函数式组件 function Demo(props) { // 每次状态改变都会调用一次函数组件(相当于render) console.log('demo'); // useState 接收一个参数,是状态的初始值 // 返回值是一个数组,数组内只有两个值,第一个是状态,第二个是更新状态的方法 // 每次改变状态都会调用demo函数,但为什么useState的状态不初始化呢?是因为react底层做处理了,当第一次调用useState后,就会把值存起来 const [count, setCount] = React.useState(0); function add() { // setCount(count+1); // 第一种写法 setCount(count => count+1); // 第二种传一个函数 console.log(count, 11111); // setState是异步的 } // useEffect hooks 可以模拟出componentDidMount、componentDidUpdate、componentWillUnmount三个生命周期 // 有两个参数,第一个参数是一个函数,第二个函数是指定要监听的数据,是一个数组 // 当没有指定第二个参数时,相当于检测所有人,每次有状态更新就会执行第一个参数(方法),相当于componentDidUpdate // 如果第二个参数传了,一个空数组,就代表谁都不监听,只有在组件挂载的时候调用一次,相当于componentDidMount钩子 // 第一个参数(是个函数)返回的函数,相当于componentWillUnmount React.useEffect(() => { console.log('监听所有状态,相当于componentDidUpdate'); }); React.useEffect(() => { console.log('只有页面挂载时调用,相当于componentDidMount生命周期钩子'); let timer = setInterval(() => { setCount(count => count+1); }, 1000); return () => { console.log('组件卸载前会执行,相当于componentWillUnmount'); clearInterval(timer) } }, []) function unmount() { const { root } = props; root.unmount(); // 18版本 } // useRef 可以在函数组件中存储/查找组件内的标签或任意其它数据 // 功能和React.createRef一样 const myRef = React.useRef(); function show() { alert(myRef.current.value) } return ( <div> <input ref={myRef} type="text" /> <h1>当前求和为:{count}</h1> <button onClick={add}>加1</button> <button onClick={unmount}>卸载组件</button> <button onClick={show}>点击提示数据</button> </div> ) } export default Demo
四、Fragment
Fragment 碎片,组件都需要有一个顶层元素,而且只有一个,但有时候,我们不想让组件有顶层元素,想成为父组件的直接子元素,那么Fragment就可以充当顶层元素,而且不会渲染。
import React, { Component, Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
// Fragment标签在渲染时,react会把它丢去,Fragment只能接受key一个属性
<Fragment key={1}>
<input type="text" />
<input type="text" />
</Fragment>
// 写一个空标签和Fragment的效果一样,但是空标签不能加key以及任何属性
// <>
// <input type="text" />
// <input type="text" />
// </>
)
}
}
五、Context
React.createContext
一种组件间通信方式,常用语【祖组件】与【后代组件】间的通信
在应用开发中,一般不用context,一般用它去封装插件
import React, { Component } from 'react'
import './index.css'
// 创建context对象
const MyContext = React.createContext();
export default class A extends Component {
state = { username: 'tom', age: 18 }
changeName = () => {
this.setState({username: 'jack'})
}
render() {
const { username, age } = this.state;
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<MyContext.Provider value={{ username, age }}>
<B/>
</MyContext.Provider>
<button onClick={this.changeName}>改名</button>
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<h4>我从A组件接收到的用户名是:{'xxx'}</h4>
<C/>
</div>
)
}
}
// class C extends Component {
// // 声明接收context,之后才能从this.context中拿到值
// static contextType = MyContext; // 只能是类式组件使用
// render() {
// return (
// <div className="grand">
// <h3>我是C组件</h3>
// <h4>我从A组件接收到的用户名是:{this.context.username}, 年龄是:{this.context.age}</h4>
// </div>
// )
// }
// }
function C() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名是:
{/* Consumer 函数式组件和类式组件都可以用 */}
<MyContext.Consumer>
{ value => `${value.username}, 年龄是:${value.age}` }
</MyContext.Consumer>
</h4>
</div>
)
}
六、pureComponent
组件优化:
1、只要调用setState(),即使不改变状态数据,组件也会调用render
2、只要当前组件调用render、就会调用子组件的render,效率低(即使子组件没用父组件的值)
原因是component中的shouldComponentUpdate总是返回true
怎么解决呢?
1、手动对比props、state的值,如果变了让组件更新,如果没变,就不让更新,但是属性多了,就很麻烦
2、React.pureComponent会自动帮我们解决
import React, { Component, PureComponent } from 'react'
import './index.css'
// PureComponent 可以解决以上问题
export default class Prent extends PureComponent {
state = { carName: '奔驰c63'}
changeCar = () => {
this.setState({carName: '迈巴赫'})
}
// 手动解决组件render问题
// shouldComponentUpdate(nextProps, nextState) {
// console.log(nextProps, nextState); // 将要变成的数据
// console.log(this.props, this.state); // 当前的数据
// return !(nextState.carName === this.state.carName);
// }
render() {
console.log('parent--render');
const { carName } = this.state;
return (
<div className="parent">
<h3>我是prarent组件</h3>
<span>我的车名字是: {carName}</span><br />
<button onClick={this.changeCar}>点我换车</button>
<Child carName={'奥拓'} />
</div>
)
}
}
class Child extends PureComponent {
render() {
console.log('child--render');
return (
<div className="child">
<h3>我是child组件</h3>
<sapn>我接到的车是:{this.props.carName}</sapn>
</div>
)
}
}
七、renderProps (类似vue中的slot)
react中类似slot的技术有两种,一种是childrenProps、另一种是renderProps
renderProps可以拿到父组件内部的状态传给子组件
组件嵌套有两种形式
第一种形式childrenProps
<A>
<B/>
<A/>
// A组件内
{this.props.children}
如果用第一种形式,有一个问题,如果A内有状态需要传给B,就没有办法了。
这里就请出了renderProps,renderProps其实就是给组件传一个名为render的props,值为一个函数,该函数在组件内部执行,可以把组件内部的状态带过来
import React, { Component } from 'react'
import './index.css'
export default class parent extends Component {
render() {
return (
<div className="parent">
<h3>我是parent组件</h3>
{/* <A>
<B />
</A> */}
{/* 渲染单个子组件 */}
{/* <A render={(name) => <B name={name} />} /> */}
{/* 渲染多个子组件 */}
<A render={
(name) =>
<React.Fragment>
<B name={name} />
<C name={name} />
</React.Fragment>
} />
</div>
)
}
}
class A extends Component {
state = {name: 'tom'}
render() {
return (
<div className="a">
<h3>我是A组件</h3>
{/* {this.props.children} */}
{this.props.render(this.state.name)}
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="b">
<h3>我是B组件, 我接收到的name是:{this.props.name}</h3>
</div>
)
}
}
class C extends Component {
render() {
return (
<div className="c">
<h3>我是C组件, 我接收到的name是:{this.props.name}</h3>
</div>
)
}
}
这里推荐一个启动静态服务器的插件:serve
之前有用过http-server,这里又有一个
npm i serve -g
项目打包后,执行 serve build(文件路径) 就可以启动起来一个服务器
八、错误边界 ErrorBoundary
程序内如果某个组件出现错误,会导致整个系统不能运行,错误边界就是把错误限制在某个组件,不让影响整个系统
处理错误边界都是在父组件内处理
用到两个钩子函数:
import React, { Component } from 'react'
import Child from './children'
import './index.css'
export default class Parent extends Component {
state={
hasError: '', // 用于标识子组件是否产生错误
}
// static getDerivedStateFromProps (当组件的状态完全由props传来时)
// 子组件报错时会调用这个钩子,而且把错误信息传来了
// getDerivedState 英文意思是获取一个衍生的状态
// 错误边界始终找出错组件的父组件去处理的
static getDerivedStateFromError(error) {
return { hasError: error }
}
// 渲染组件时出错
componentDidCatch() {
console.log('渲染组件时出错');
// 一般在这个钩子里统计错误次数,反馈给服务器,通知程序人员进行bug解决
}
render() {
return (
<div className="parent">
<h3>我是parent组件</h3>
{
this.state.hasError
? <h4>当前子组件报错,稍后再试</h4>
: <Child />
}
</div>
)
}
}
-

浙公网安备 33010602011771号