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> ) } }
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2019-10-22 pc页面自动缩放到手机端