React扩展
setState
对象式 setState
首先在我们以前的认知中,setState
是用来更新状态的,我们一般给它传递一个对象,就像这样
this.setState({
count: count + 1
})
这样每次更新都会让 count
的值加 1。这也是我们最常做的东西
那我们需要在控制台输出,要如何实现呢?
我们会考虑在 setState
更新之后 log
一下
add = () => {
const { count } = this.state // count = 1
this.setState({
count: count + 1
})
console.log(this.state.count);// 1
}
我们发现显示的 count 和我们控制台输出的 count 值是不一样的
这是因为,我们调用的 setState 是同步事件,但是它的作用是让 React 去更新数据,而 React 不会立即的去更新数据,这是一个异步的任务,因此我们输出的 count 值会是状态更新之前的数据。“React 状态更新是异步的”那我们要如何实现同步呢?
其实在 setState
调用的第二个参数,我们可以接收一个函数,这个函数会在状态更新完毕并且界面更新之后调用,我们可以试试
add = () => {
const { count } = this.state
this.setState({
count: count + 1
}, () => {
console.log(this.state.count)
})
}
这样我们就能成功的获取到最新的数据了,如果有这个需求我们可以在第二个参数输出噢~
函数式 setState
函数式的 setState 也是接收两个参数
第一个参数是 updater ,它是一个能够返回 stateChange 对象的函数
第二个参数是一个回调函数,用于在状态更新完毕,界面也更新之后调用
与对象式 setState 不同的是,我们传递的第一个参数 updater 可以接收到2个参数 state 和 props
add = () => {
this.setState((state,props) => ({ count: state.count + 1 }),()=>{
})
}
我们在第一个参数中传入了一个函数,这个函数可以接收到 state
,我们通过更新 state
中的 count
值,来驱动页面的更新
利用函数式 setState
的优势还是很不错的,可以直接获得 state
和 props
LazyLoad
懒加载在 React 中用的最多的就是路由组件了,页面刷新时,所有的页面都会重新加载,这并不是我们想要的,我们想要实现点击哪个路由链接再加载即可,这样避免了不必要的加载
如果我们有 100 个路由组件,但是用户只点击了几个,这就会有很大的消耗,因此我们需要做懒加载处理,我们点击哪个时,才去加载哪一个
首先我们需要从 react
库中暴露一个 lazy
函数
import React, { Component ,lazy} from 'react';
然后我们需要更改引入组件的方式
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
当我们网速慢的时候,路由组件就会有可能加载不出来,页面就会白屏,这时需要我们来指定一个路由组件加载的东西,相对于 loading
<Suspense fallback={<h1>loading</h1>}>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Suspense>
所以实现懒加载需要用到 lazy 和 Suspense
注意:因为 loading 是作为一个兜底的存在,因此 loading 是 必须提前引入的,不能懒加载
常用Hooks
useState
hooks
解决了函数式组件和类式组件的差异,让函数式组件拥有了类式组件所拥有的 state
,同时新增了一些 API ,让函数式组件,变得更加的灵活
首先我们需要明确一点,函数式组件没有自己的 this
function Demo() {
const [count, setCount] = React.useState(0)
console.log(count, setCount);
function add() {
setCount(count + 1)
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我加1</button>
</div>
)
}
export default Demo
利用函数式组件完成的 点我加1 案例
这里利用了一个 Hook :useState
它让函数式组件能够维护自己的 state ,它接收一个参数,作为初始化 state 的值,赋值给 count,因此 useState 的初始值只有第一次有效,它所映射出的两个变量 count 和 setCount 我们可以理解为 setState 来使用
useState 能够返回一个数组,第一个元素是 state ,第二个是更新 state 的函数
useEffect
在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount ,能够在组件挂载完成后执行一些东西
在函数式组件中也可以实现,它采用的是 effectHook ,它的语法更加的简单,同时融合了 componentDidUpdata 生命周期,极大的方便了我们的开发
React.useEffect(() => { console.log('被调用了');})
由于函数的特性,我们可以在函数中随意的编写函数,这里我们调用了 useEffect 函数,这个函数有多个功能
当我们像上面代码那样使用时,它相当于 componentDidUpdata 和 componentDidMount 一同使用,也就是在组件挂载和组件更新的时候都会调用这个函数
它还可以接收第二个参数,这个参数表示它要监测的数据,也就是他要监视哪个数据的变化
当我们不需要监听任何状态变化的时候,我们可以就传递一个空数组,这样它就能当作componentMidMount
来使用
React.useEffect(() => { console.log('被调用了');}, [])
这样我们只有在组件第一次挂载的时候触发
当然当页面中有多个数据源时,我们也可以选择个别的数据进行监测以达到我们想要的效果
React.useEffect(() => { console.log('被调用了');}, [count])
这样,我们就只监视 count 数据的变化
当我们想要在卸载一个组件之前进行一些清除定时器的操作,在类式组件中,我们会调用生命周期钩子 componentDidUnmount 来实现,在函数式组件中,我们的写法更为简单,我们直接在 useEffect 的第一个参数的返回值中实现即可
也就是说,第一个参数的函数体相当于 componentDidMount 返回体相当于 componentDidUnmount ,这样我们就能实现在组件即将被卸载时输出一些东西了
实现卸载
function unmount() { ReactDOM.unmountComponentAtNode(document.getElementById("root"))}
卸载前输出
React.useEffect(() => { console.log('被调用了'); return () => { console.log('我要被卸载了'); }}, [count])
因此
useEffect
相当于三个生命周期钩子,componentDidMount
、componentDidUpdata
、componentDidUnmount
useRef
当我们想要获取组件内的信息时,在类式组件中,我们会采用 ref
的方式来获取。在函数式组件中,我们可以采用也可以采用 ref
但是,我们需要采用 useRef
函数来创建一个 ref 容器,这和 createRef
很类似。
myRef = useRef()
<input type="text" ref={myRef} />
获取 ref 值
function show() { alert(myRef.current.value)}
即可成功的获取到 input 框中的值
Fragment
Fragment和vue中的template一样
Context
仅适用于类式组件
context专用于祖孙之间数据的传输。在传输时可以不影响其中的父组件
首先我们需要引入一个 MyContext
组件,我们需要引用MyContext
下的 Provider
const MyContext = React.createContext();
const { Provider } = MyContext;
用 Provider
标签包裹 A组件内的 B 组件,并通过 value
值,将数据传递给子组件,这样以 A 组件为父代组件的所有子组件都能够接受到数据
//A组件内
<Provider value={{ username, age }}>
<B />
</Provider>
我们需要在使用数据的组件中引入 MyContext
static contextType = MyContext;
在使用时,直接从 this.context
上取值即可
const {username,age} = this.context
但函数式组件没有自己的this,所以React提供了一个解决方案
由于函数式组件没有自己 this
,所以我们不能通过 this.context
来获取数据
这里我们需要从 Context
身上暴露出一个 Consumer
const { Provider ,Consumer} = MyContext;
然后通过 value
取值即可
function C() {
return (
<div>
<h3>我是C组件,我从A接收到的数据 </h3>
<Consumer>
{(value) => {
return `${value.username},年龄是${value.age}`;
}}
</Consumer>
</div>
);}
因此想要在函数式组件中使用,需要引入
Consumer
PureComponent
在我们之前一直写的代码中,我们一直使用的Component
是有问题存在的
- 只要执行
setState
,即使不改变状态数据,组件也会调用render
- 当前组件状态更新,也会引起子组件
render
而我们想要的是只有组件的 state
或者 props
数据发生改变的时候,再调用 render
我们可以采用重写 shouldComponentUpdate
的方法,但是这个方法不能根治这个问题,当状态很多时,我们没有办法增加判断
我们可以采用 PureComponent
我们可以从 react
身上暴露出 PureComponent
而不使用 Component
import React, { PureComponent } from 'react'
PureComponent
会对比当前对象和下一个状态的 prop
和 state
,而这个比较属于浅比较,比较基本数据类型是否相同,而对于引用数据类型,比较的是它的引用地址是否相同,这个比较与内容无关
render props
采用 render props 技术,我们可以向组件内部动态传入带有内容的结构
当我们在一个组件标签中填写内容时,这个内容会被定义为 children props,我们可以通过
this.props.children
来获取
<A>hello</A>
这个 hello 我们就可以通过 children 来获取
而我们所说的 render props
就是在组件标签中传入一个 render 方法,又因为属于 props ,因而被叫做了 render props
<A render={(name) => <C name={name} />} />
你可以把 render 看作是 props,只是它有特殊作用,当然它也可以用其他名字来命名
在上面的代码中,我们需要在 A 组件中预留出 C 组件渲染的位置 在需要的位置上加上{this.props.render(name)}
那我们在 C 组件中,如何接收 A 组件传递的 name 值呢?通过 this.props.name
的方式
ErrorBoundary
当不可控因素导致数据不正常时,我们不能直接将报错页面呈现在用户的面前,由于我们没有办法给每一个组件、每一个文件添加判断,来确保正常运行,这样很不现实,因此我们要用到错误边界技术
错误边界就是让这块组件报错的影响降到最小,不要影响到其他组件或者全局的正常运行
例如 A 组件报错了,我们可以在 A 组件内添加一小段的提示,并把错误控制在 A 组件内,不影响其他组件
- 我们要对容易出错的组件的父组件做手脚,而不是组件本身
我们在父组件中通过 getDerivedStateFromError
来配置子组件出错时的处理函数
static getDerivedStateFromError(error) {
console.log(error);
return { hasError: error }
}
我们可以将 hasError
配置到状态当中,当 hasError
状态改变成 error
时,表明有错误发生,我们需要在组件中通过判断 hasError
值,来指定是否显示子组件
{this.state.hasError ? <h2>出错啦</h2> : <Child />}
可以在 componentDidCatch
中统计错误次数,通知编码人员进行 bug 解决