不少同窗用react
开发的时候,真正用到的React
的api
少之又少,基本停留在Component
,React.memo
等层面,实际react
源码中,暴露出来的方法并很多,只是咱们平时不多用。可是React
暴露出这么多api
并不是没有用,想要玩转react
,就要明白这些API
到底是干什么的,应用场景是什么,今天就让咱们从react
到 react-dom
,一次性把react
生产环境的暴露api
复习个遍(涵盖90%+)。html
咱们把react
,API
,分为组件类,工具类,hooks
,再加上 react-dom
,一共四大方向,分别加以探讨。前端
为了能让屏幕前的你,更理解api
,我是绞尽脑汁,本文的每个api
基本都会出一个demo
演示效果,弥补一下天书般的react
文档😂😂😂,还有就是我对api
基本概念的理解。创做不易,但愿屏幕前的你能给笔者赏个赞,以此鼓励我继续创做前端硬文。node
老规矩,咱们带着疑问开始今天的阅读(自测掌握程度)?react
react
暴露的api
有哪些,该如何使用?react
提供了哪些自测性能的手段?ref
既然不能用在函数组件中,那么父组件如何控制函数子组件内的state
和方法?createElement
和cloneElement
有什么区别,应用场景是什么?react
内置的children
遍历方法,和数组方法,有什么区别?react
怎么将子元素渲染到父元素以外的指定容器中?我相信读完这篇文章,这些问题全都会迎刃而解?redux
组件类,详细分的话有三种类,第一类说白了就是我平时用于继承的基类组件Component
,PureComponent
,还有就是react
提供的内置的组件,好比Fragment
,StrictMode
,另外一部分就是高阶组件forwardRef
,memo
等。设计模式
Component
是class
组件的根基。类组件一切始于Component
。对于React.Component
使用,咱们没有什么好讲的。咱们这里重点研究一下react
对Component
作了些什么。前端工程化
react/src/ReactBaseClasses.js
api
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
复制代码
这就是Component
函数,其中updater
对象上保存着更新组件的方法。数组
咱们声明的类组件是何时以何种形式被实例化的呢?浏览器
react-reconciler/src/ReactFiberClassComponent.js
constructClassInstance
function constructClassInstance( workInProgress, ctor, props ){
const instance = new ctor(props, context);
instance.updater = {
isMounted,
enqueueSetState(){
/* setState 触发这里面的逻辑 */
},
enqueueReplaceState(){},
enqueueForceUpdate(){
/* forceUpdate 触发这里的逻辑 */
}
}
}
复制代码
对于Component
, react
处理逻辑仍是很简单的,实例化咱们类组件,而后赋值updater
对象,负责组件的更新。而后在组件各个阶段,执行类组件的render
函数,和对应的生命周期函数就能够了。
PureComponent
和 Component
用法,差很少同样,惟一不一样的是,纯组件PureComponent
会浅比较,props
和state
是否相同,来决定是否从新渲染组件。因此通常用于性能调优,减小render次数。
什么叫作浅比较,我这里举个列子:
class Index extends React.PureComponent{
constructor(props){
super(props)
this.state={
data:{
name:'alien',
age:28
}
}
}
handerClick= () =>{
const { data } = this.state
data.age++
this.setState({ data })
}
render(){
const { data } = this.state
return <div className="box" > <div className="show" > <div> 你的姓名是: { data.name } </div> <div> 年龄: { data.age }</div> <button onClick={ this.handerClick } >age++</button> </div> </div>
}
}
复制代码
点击按钮,没有任何反应,由于
PureComponent
会比较两次data
对象,都指向同一个data
,没有发生改变,因此不更新视图。
解决这个问题很简单,只须要在handerClick
事件中这么写:
this.setState({ data:{...data} })
复制代码
浅拷贝就能根本解决问题。
React.memo
和PureComponent
做用相似,能够用做性能优化,React.memo
是高阶组件,函数组件和类组件均可以使用, 和区别PureComponent
是 React.memo
只能对props
的状况肯定是否渲染,而PureComponent
是针对props
和state
。
React.memo
接受两个参数,第一个参数原始组件自己,第二个参数,能够根据一次更新中props
是否相同决定原始组件是否从新渲染。是一个返回布尔值,true
证实组件无须从新渲染,false
证实组件须要从新渲染,这个和类组件中的shouldComponentUpdate()
正好相反 。
React.memo: 第二个参数 返回 true
组件不渲染 , 返回 false
组件从新渲染。 shouldComponentUpdate: 返回 true
组件渲染 , 返回 false
组件不渲染。
接下来咱们作一个场景,控制组件在仅此一个props
数字变量,必定范围渲染。
例子🌰:
控制 props
中的 number
:
1 只有 number
更改,组件渲染。
2 只有 number
小于 5 ,组件渲染。
function TextMemo(props){
console.log('子组件渲染')
if(props)
return <div>hello,world</div>
}
const controlIsRender = (pre,next)=>{
if(pre.number === next.number ){ // number 不改变 ,不渲染组件
return true
}else if(pre.number !== next.number && next.number > 5 ) { // number 改变 ,但值大于5 , 不渲染组件
return true
}else { // 不然渲染组件
return false
}
}
const NewTexMemo = memo(TextMemo,controlIsRender)
class Index extends React.Component{
constructor(props){
super(props)
this.state={
number:1,
num:1
}
}
render(){
const { num , number } = this.state
return <div> <div> 改变num:当前值 { num } <button onClick={ ()=>this.setState({ num:num + 1 }) } >num++</button> <button onClick={ ()=>this.setState({ num:num - 1 }) } >num--</button> </div> <div> 改变number: 当前值 { number } <button onClick={ ()=>this.setState({ number:number + 1 }) } > number ++</button> <button onClick={ ()=>this.setState({ number:number - 1 }) } > number -- </button> </div> <NewTexMemo num={ num } number={number} /> </div>
}
}
复制代码
效果:
完美达到了效果,React.memo
必定程度上,能够等价于组件外部使用shouldComponentUpdate
,用于拦截新老props
,肯定组件是否更新。
官网对forwardRef
的概念和用法很笼统,也没有给定一个具体的案例。不少同窗不知道 forwardRef
具体怎么用,下面我结合具体例子给你们讲解forwardRef
应用场景。
1 转发引入Ref
这个场景实际很简单,好比父组件想获取孙组件,某一个dom
元素。这种隔代ref
获取引用,就须要forwardRef
来助力。
function Son (props){
const { grandRef } = props
return <div> <div> i am alien </div> <span ref={grandRef} >这个是想要获取元素</span> </div>
}
class Father extends React.Component{
constructor(props){
super(props)
}
render(){
return <div> <Son grandRef={this.props.grandRef} /> </div>
}
}
const NewFather = React.forwardRef((props,ref)=><Father grandRef={ref} {...props} /> )
class GrandFather extends React.Component{
constructor(props){
super(props)
}
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div> <NewFather ref={(node)=> this.node = node } /> </div>
}
}
复制代码
效果
react
不容许ref
经过props
传递,由于组件上已经有 ref
这个属性,在组件调和过程当中,已经被特殊处理,forwardRef
出现就是解决这个问题,把ref
转发到自定义的forwardRef
定义的属性上,让ref
,能够经过props
传递。
2 高阶组件转发Ref
一文吃透hoc
文章中讲到,因为属性代理的hoc
,被包裹一层,因此若是是类组件,是经过ref
拿不到原始组件的实例的,不过咱们能够经过forWardRef
转发ref
。
function HOC(Component){
class Wrap extends React.Component{
render(){
const { forwardedRef ,...otherprops } = this.props
return <Component ref={forwardedRef} {...otherprops} />
}
}
return React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> )
}
class Index extends React.Component{
componentDidMount(){
console.log(666)
}
render(){
return <div>hello,world</div>
}
}
const HocIndex = HOC(Index,true)
export default ()=>{
const node = useRef(null)
useEffect(()=>{
/* 就能够跨层级,捕获到 Index 组件的实例了 */
console.log(node.current.componentDidMount)
},[])
return <div><HocIndex ref={node} /></div>
}
复制代码
如上,解决了高阶组件引入Ref
的问题。
React.lazy 和 Suspense 技术还不支持服务端渲染。若是你想要在使用服务端渲染的应用中使用,咱们推荐 Loadable Components 这个库
React.lazy
和Suspense
配合一块儿用,可以有动态加载组件的效果。React.lazy
接受一个函数,这个函数须要动态调用 import()
。它必须返回一个 Promise
,该 Promise
须要 resolve
一个 default export
的 React
组件。
咱们模拟一个动态加载的场景。
父组件
import Test from './comTest'
const LazyComponent = React.lazy(()=> new Promise((resolve)=>{
setTimeout(()=>{
resolve({
default: ()=> <Test />
})
},2000)
}))
class index extends React.Component{
render(){
return <div className="context_box" style={ { marginTop :'50px' } } > <React.Suspense fallback={ <div className="icon" ><SyncOutlined spin /></div> } > <LazyComponent /> </React.Suspense> </div>
}
}
复制代码
咱们用setTimeout
来模拟import
异步引入效果。
Test
class Test extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
console.log('--componentDidMount--')
}
render(){
return <div> <img src={alien} className="alien" /> </div>
}
}
复制代码
效果
何为Suspense
, Suspense
让组件“等待”某个异步操做,直到该异步操做结束便可渲染。
用于数据获取的 Suspense
是一个新特性,你可使用 <Suspense>
以声明的方式来“等待”任何内容,包括数据。本文重点介绍它在数据获取的用例,它也能够用于等待图像、脚本或其余异步的操做。
上面讲到高阶组件lazy
时候,已经用 lazy
+ Suspense
模式,构建了异步渲染组件。咱们看一下官网文档中的案例:
const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懒加载
<Suspense fallback={<Spinner />}> <ProfilePage /> </Suspense>
复制代码
react
不容许一个组件返回多个节点元素,好比说以下状况
render(){
return <li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
}
复制代码
若是咱们想解决这个状况,很简单,只须要在外层套一个容器元素。
render(){
return <div> <li> 🍎🍎🍎 </li> <li> 🍌🍌🍌 </li> <li> 🍇🍇🍇 </li> </div>
}
复制代码
可是咱们不指望,增长额外的dom
节点,因此react
提供Fragment
碎片概念,可以让一个组件返回多个元素。 因此咱们能够这么写
<React.Fragment>
<li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
</React.Fragment>
复制代码
还能够简写成:
<>
<li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
</>
复制代码
和Fragment
区别是,Fragment
能够支持key
属性。<></>
不支持key
属性。
舒适提示。咱们经过map
遍历后的元素,react
底层会处理,默认在外部嵌套一个<Fragment>
。
好比:
{
[1,2,3].map(item=><span key={item.id} >{ item.name }</span>)
}
复制代码
react
底层处理以后,等价于:
<Fragment>
<span></span>
<span></span>
<span></span>
</Fragment>
复制代码
Profiler
这个api
通常用于开发阶段,性能检测,检测一次react
组件渲染用时,性能开销。
Profiler
须要两个参数:
第一个参数:是 id
,用于表识惟一性的Profiler
。
第二个参数:onRender
回调函数,用于渲染完成,接受渲染参数。
实践:
const index = () => {
const callback = (...arg) => console.log(arg)
return <div > <div > <Profiler id="root" onRender={ callback } > <Router > <Meuns/> <KeepaliveRouterSwitch withoutRoute > { renderRoutes(menusList) } </KeepaliveRouterSwitch> </Router> </Profiler> </div> </div>
}
复制代码
结果
onRender
root
-> Profiler
树的 id
。mount
-> mount
挂载 , update
渲染了。6.685000262223184
-> 更新 committed
花费的渲染时间。4.430000321008265
-> 渲染整颗子树须要的时间689.7299999836832
-> 本次更新开始渲染的时间698.5799999674782
-> 本次更新committed 的时间set{}
-> 本次更新的 interactions
的集合尽管 Profiler 是一个轻量级组件,咱们依然应该在须要时才去使用它。对一个应用来讲,每添加一些都会给 CPU 和内存带来一些负担。
StrictMode
见名知意,严格模式,用于检测react
项目中的潜在的问题,。与 Fragment
同样, StrictMode
不会渲染任何可见的 UI
。它为其后代元素触发额外的检查和警告。
严格模式检查仅在开发模式下运行;它们不会影响生产构建。
StrictMode
目前有助于:
ref API
的警告findDOMNode
方法的警告context API
实践:识别不安全的生命周期
对于不安全的生命周期,指的是UNSAFE_componentWillMount
,UNSAFE_componentWillReceiveProps
, UNSAFE_componentWillUpdate
外层开启严格模式:
<React.StrictMode>
<Router > <Meuns/> <KeepaliveRouterSwitch withoutRoute > { renderRoutes(menusList) } </KeepaliveRouterSwitch> </Router>
</React.StrictMode>
复制代码
咱们在内层组件中,使用不安全的生命周期:
class Index extends React.Component{
UNSAFE_componentWillReceiveProps(){
}
render(){
return <div className="box" />
}
}
复制代码
效果:
接下来咱们一块儿来探究一下react
工具类函数的用法。
一提到createElement
,就不禁得和JSX
联系一块儿。咱们写的jsx
,最终会被 babel
,用createElement
编译成react
元素形式。我写一个组件,咱们看一下会被编译成什么样子,
若是咱们在render
里面这么写:
render(){
return <div className="box" > <div className="item" >生命周期</div> <Text mes="hello,world" /> <React.Fragment> Flagment </React.Fragment> { /* */ } text文本 </div>
}
复制代码
会被编译成这样:
render() {
return React.createElement("div", { className: "box" },
React.createElement("div", { className: "item" }, "\u751F\u547D\u5468\u671F"),
React.createElement(Text, { mes: "hello,world" }),
React.createElement(React.Fragment, null, " Flagment "),
"text\u6587\u672C");
}
复制代码
固然咱们能够不用jsx
模式,而是直接经过createElement
进行开发。
createElement
模型:
React.createElement(
type,
[props],
[...children]
)
复制代码
createElement
参数:
**第一个参数:**若是是组件类型,会传入组件,若是是dom
元素类型,传入div
或者span
之类的字符串。
第二个参数::第二个参数为一个对象,在dom
类型中为属性,在组件
类型中为props。
其余参数:,依次为children
,根据顺序排列。
createElement作了些什么?
通过createElement
处理,最终会造成 $$typeof = Symbol(react.element)
对象。对象上保存了该react.element
的信息。
可能有的同窗还傻傻的分不清楚cloneElement
和createElement
区别和做用。
createElement
把咱们写的jsx
,变成element
对象; 而cloneElement
的做用是以 element
元素为样板克隆并返回新的 React
元素。返回元素的 props
是将新的 props
与原始元素的 props
浅层合并后的结果。
那么cloneElement
感受在咱们实际业务组件中,可能没什么用,可是在一些开源项目,或者是公共插槽组件中用处仍是蛮大的,好比说,咱们能够在组件中,劫持children element
,而后经过cloneElement
克隆element
,混入props
。经典的案例就是 react-router
中的Swtich
组件,经过这种方式,来匹配惟一的 Route
并加以渲染。
咱们设置一个场景,在组件中,去劫持children
,而后给children
赋能一些额外的props
:
function FatherComponent({ children }){
const newChildren = React.cloneElement(children, { age: 18})
return <div> { newChildren } </div>
}
function SonComponent(props){
console.log(props)
return <div>hello,world</div>
}
class Index extends React.Component{
render(){
return <div className="box" > <FatherComponent> <SonComponent name="alien" /> </FatherComponent> </div>
}
}
复制代码
打印:
完美达到了效果!
createContext
用于建立一个Context
对象,createContext
对象中,包括用于传递 Context
对象值 value
的Provider
,和接受value
变化订阅的Consumer
。
const MyContext = React.createContext(defaultValue)
复制代码
createContext
接受一个参数defaultValue
,若是Consumer
上一级一直没有Provider
,则会应用defaultValue
做为value
。只有当组件所处的树中没有匹配到 Provider
时,其 defaultValue
参数才会生效。
咱们来模拟一个 Context.Provider
和Context.Consumer
的例子:
function ComponentB(){
/* 用 Consumer 订阅, 来自 Provider 中 value 的改变 */
return <MyContext.Consumer> { (value) => <ComponentA {...value} /> } </MyContext.Consumer>
}
function ComponentA(props){
const { name , mes } = props
return <div> <div> 姓名: { name } </div> <div> 想对你们说: { mes } </div> </div>
}
function index(){
const [ value , ] = React.useState({
name:'alien',
mes:'let us learn React '
})
return <div style={{ marginTop:'50px' }} > <MyContext.Provider value={value} > <ComponentB /> </MyContext.Provider> </div>
}
复制代码
打印结果:
Provider
和Consumer
的良好的特性,能够作数据的存和取,Consumer
一方面传递value
,另外一方面能够订阅value
的改变。
Provider
还有一个特性能够层层传递value
,这种特性在react-redux
中表现的淋漓尽致。
React.createFactory(type)
复制代码
返回用于生成指定类型 React 元素的函数。类型参数既能够是标签名字符串(像是 'div
' 或 'span
'),也能够是 React 组件 类型 ( class
组件或函数组件),或是 React fragment
类型。
使用:
const Text = React.createFactory(()=><div>hello,world</div>)
function Index(){
return <div style={{ marginTop:'50px' }} > <Text/> </div>
}
复制代码
效果
报出警告,这个api
将要被废弃,咱们这里就很少讲了,若是想要达到一样的效果,请用React.createElement
createRef
能够建立一个 ref
元素,附加在react
元素上。
用法:
class Index extends React.Component{
constructor(props){
super(props)
this.node = React.createRef()
}
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={this.node} > my name is alien </div>
}
}
复制代码
我的以为createRef
这个方法,很鸡肋,咱们彻底能够class
类组件中这么写,来捕获ref
。
class Index extends React.Component{
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={(node)=> this.node } > my name is alien </div>
}
}
复制代码
或者在function
组件中这么写:
function Index(){
const node = React.useRef(null)
useEffect(()=>{
console.log(node.current)
},[])
return <div ref={node} > my name is alien </div>
}
复制代码
这个方法能够用来检测是否为react element
元素,接受待验证对象,返回true
或者false
。这个api可能对于业务组件的开发,做用不大,由于对于组件内部状态,都是已知的,咱们根本就不须要去验证,是不是react element
元素。 可是,对于一块儿公共组件或是开源库,isValidElement
就颇有做用了。
实践
咱们作一个场景,验证容器组件的全部子组件,过滤到非react element
类型。
没有用isValidElement
验证以前:
const Text = () => <div>hello,world</div>
class WarpComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return this.props.children
}
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> <Text/> <div> my name is alien </div> Let's learn react together! </WarpComponent> </div>
}
复制代码
过滤以前的效果
咱们用isValidElement
进行react element
验证:
class WarpComponent extends React.Component{
constructor(props){
super(props)
this.newChidren = this.props.children.filter(item => React.isValidElement(item) )
}
render(){
return this.newChidren
}
}
复制代码
过滤以后效果
过滤掉了非react element
的 Let's learn react together!
。
接下来的五个api
都是和react.Chidren
相关的,咱们来分别介绍一下,咱们先来看看官网的描述,React.Children
提供了用于处理 this.props.children
不透明数据结构的实用方法。
有的同窗会问遍历 children
用数组方法,map
,forEach
不就能够了吗? 请咱们注意一下不透明数据结构
,什么叫作不透明结构?
咱们先看一下透明的结构:
class Text extends React.Component{
render(){
return <div>hello,world</div>
}
}
function WarpComponent(props){
console.log(props.children)
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> <Text/> <Text/> <Text/> <span>hello,world</span> </WarpComponent> </div>
}
复制代码
打印
可是咱们把Index
结构改变一下:
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map(()=><Text/>) } <span>hello,world</span> </WarpComponent> </div>
}
复制代码
打印
这个数据结构,咱们不能正常的遍历了,即便遍历也不能遍历,每个子元素。此时就须要 react.Chidren
来帮忙了。
可是咱们把WarpComponent
组件用react.Chidren
处理children
:
function WarpComponent(props){
const newChildren = React.Children.map(props.children,(item)=>item)
console.log(newChildren)
return newChildren
}
复制代码
此时就能正常遍历了,达到了预期效果。
注意 若是 children
是一个 Fragment
对象,它将被视为单一子节点的状况处理,而不会被遍历。
Children.forEach
和Children.map
用法相似,Children.map
能够返回新的数组,Children.forEach
仅停留在遍历阶段。
咱们将上面的WarpComponent
方法,用Children.forEach
改一下。
function WarpComponent(props){
React.Children.forEach(props.children,(item)=>console.log(item))
return props.children
}
复制代码
children
中的组件总数量,等同于经过 map
或 forEach
调用回调函数的次数。对于更复杂的结果,Children.count
能够返回同一级别子组件的数量。
咱们仍是把上述例子进行改造:
function WarpComponent(props){
const childrenCount = React.Children.count(props.children)
console.log(childrenCount,'childrenCount')
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map((item,index) => new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) } <span>hello,world</span> </WarpComponent> </div>
}
复制代码
效果:
Children.toArray
返回,props.children
扁平化后结果。
function WarpComponent(props){
const newChidrenArray = React.Children.toArray(props.children)
console.log(newChidrenArray,'newChidrenArray')
return newChidrenArray
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map((item,index)=>new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) } <span>hello,world</span> </WarpComponent> </div>
}
复制代码
效果:
newChidrenArray ,就是扁平化的数组结构。React.Children.toArray()
在拉平展开子节点列表时,更改 key
值以保留嵌套数组的语义。也就是说, toArray
会为返回数组中的每一个 key
添加前缀,以使得每一个元素 key
的范围都限定在此函数入参数组的对象内。
验证 children
是否只有一个子节点(一个 React
元素),若是有则返回它,不然此方法会抛出错误。
不惟一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> { new Array(3).fill(0).map((item,index)=><Text key={index} />) } <span>hello,world</span> </WarpComponent> </div>
}
复制代码
效果
惟一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} > <WarpComponent> <Text/> </WarpComponent> </div>
}
复制代码
效果
React.Children.only()
不接受 React.Children.map()
的返回值,由于它是一个数组而并非 React
元素。
对于react-hooks
,我已经写了三部曲,介绍了react-hooks
使用,自定义hooks
,以及react-hooks
原理,感兴趣的同窗能够去看看,文章末尾有连接,对于经常使用的api
,我这里参考了react-hooks
如何使用那篇文章。并作了相应精简化和一些内容的补充。
useState
能够弥补函数组件没有state
的缺陷。useState
能够接受一个初识值,也能够是一个函数action
,action
返回值做为新的state
。返回一个数组,第一个值为state
读取值,第二个值为改变state
的dispatchAction
函数。
咱们看一个例子:
const DemoState = (props) => {
/* number为此时state读取值 ,setNumber为派发更新的函数 */
let [number, setNumber] = useState(0) /* 0为初始值 */
return (<div> <span>{ number }</span> <button onClick={ ()=> { setNumber(number+1) /* 写法一 */ setNumber(number=>number + 1 ) /* 写法二 */ console.log(number) /* 这里的number是不可以即时改变的 */ } } >num++</button> </div>)
}
复制代码
useEffect
能够弥补函数组件没有生命周期的缺点。咱们能够在useEffect
第一个参数回调函数中,作一些请求数据,事件监听等操做,第二个参数做为dep
依赖项,当依赖项发生变化,从新执行第一个函数。
useEffect能够用做数据交互。
/* 模拟数据交互 */
function getUserInfo(a){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({
name:a,
age:16,
})
},500)
})
}
const DemoEffect = ({ a }) => {
const [ userMessage , setUserMessage ] :any= useState({})
const div= useRef()
const [number, setNumber] = useState(0)
/* 模拟事件监听处理函数 */
const handleResize =()=>{}
/* useEffect使用 ,这里若是不加限制 ,会是函数重复执行,陷入死循环*/
useEffect(()=>{
/* 请求数据 */
getUserInfo(a).then(res=>{
setUserMessage(res)
})
/* 操做dom */
console.log(div.current) /* div */
/* 事件监听等 */
window.addEventListener('resize', handleResize)
/* 只有当props->a和state->number改变的时候 ,useEffect反作用函数从新执行 ,若是此时数组为空[],证实函数只有在初始化的时候执行一次至关于componentDidMount */
},[ a ,number ])
return (<div ref={div} > <span>{ userMessage.name }</span> <span>{ userMessage.age }</span> <div onClick={ ()=> setNumber(1) } >{ number }</div> </div>)
}
复制代码
useEffect能够用做事件监听,还有一些基于dom
的操做。,别忘了在useEffect
第一个参数回调函数,返一个函数用于清除事件监听等操做。
const DemoEffect = ({ a }) => {
/* 模拟事件监听处理函数 */
const handleResize =()=>{}
useEffect(()=>{
/* 定时器 延时器等 */
const timer = setInterval(()=>console.log(666),1000)
/* 事件监听 */
window.addEventListener('resize', handleResize)
/* 此函数用于清除反作用 */
return function(){
clearInterval(timer)
window.removeEventListener('resize', handleResize)
}
},[ a ])
return (<div > </div>)
}
复制代码
useMemo
接受两个参数,第一个参数是一个函数,返回值用于产生保存值。 第二个参数是一个数组,做为dep
依赖项,数组里面的依赖项发生变化,从新执行第一个函数,产生新的值。
应用场景: 1 缓存一些值,避免从新执行上下文
const number = useMemo(()=>{
/** ....大量的逻辑运算 **/
return number
},[ props.number ]) // 只有 props.number 改变的时候,从新计算number的值。
复制代码
2 减小没必要要的dom
循环
/* 用 useMemo包裹的list能够限定当且仅当list改变的时候才更新此list,这样就能够避免selectList从新循环 */
{useMemo(() => (
<div>{ selectList.map((i, v) => ( <span className={style.listSpan} key={v} > {i.patentName} </span> ))} </div>
), [selectList])}
复制代码
3 减小子组件渲染
/* 只有当props中,list列表改变的时候,子组件才渲染 */
const goodListChild = useMemo(()=> <GoodList list={ props.list } /> ,[ props.list ])
复制代码
useMemo
和 useCallback
接收的参数都是同样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo
返回的是函数运行的结果, useCallback
返回的是函数。 返回的callback
能够做为props
回调函数传递给子组件。
/* 用react.memo */
const DemoChildren = React.memo((props)=>{
/* 只有初始化的时候打印了 子组件更新 */
console.log('子组件更新')
useEffect(()=>{
props.getInfo('子组件')
},[])
return <div>子组件</div>
})
const DemoUseCallback=({ id })=>{
const [number, setNumber] = useState(1)
/* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) } 通过处理赋值给 getInfo */
const getInfo = useCallback((sonName)=>{
console.log(sonName)
},[id])
return <div> {/* 点击按钮触发父组件更新 ,可是子组件没有更新 */} <button onClick={ ()=>setNumber(number+1) } >增长</button> <DemoChildren getInfo={getInfo} /> </div>
}
复制代码
useRef
的做用:
dom
元素,或者class
组件实例 。react-hooks原理
文章中讲过,建立useRef
时候,会建立一个原始对象,只要函数组件不被销毁,原始对象就会一直存在,那么咱们能够利用这个特性,来经过useRef
保存一些数据。const DemoUseRef = ()=>{
const dom= useRef(null)
const handerSubmit = ()=>{
/* <div >表单组件</div> dom 节点 */
console.log(dom.current)
}
return <div> {/* ref 标记当前dom节点 */} <div ref={dom} >表单组件</div> <button onClick={()=>handerSubmit()} >提交</button> </div>
}
复制代码
useEffect
执行顺序: 组件更新挂载完成 -> 浏览器 dom
绘制完成 -> 执行 useEffect
回调。 useLayoutEffect
执行顺序: 组件更新挂载完成 -> 执行 useLayoutEffect
回调-> 浏览器dom
绘制完成。
因此说 useLayoutEffect
代码可能会阻塞浏览器的绘制 。咱们写的 effect
和 useLayoutEffect
,react
在底层会被分别打上PassiveEffect
,HookLayout
,在commit
阶段区分出,在什么时机执行。
const DemoUseLayoutEffect = () => {
const target = useRef()
useLayoutEffect(() => {
/*咱们须要在dom绘制以前,移动dom到制定位置*/
const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
animate(target.current,{ x,y })
}, []);
return (
<div > <span ref={ target } className="animate"></span> </div>
)
}
复制代码
在react-hooks
原理那篇文章中讲解到,useState
底层就是一个简单版的useReducer
useReducer
接受的第一个参数是一个函数,咱们能够认为它就是一个 reducer
, reducer
的参数就是常规 reducer
里面的 state
和 action
,返回改变后的 state
, useReducer
第二个参数为 state
的初始值 返回一个数组,数组的第一项就是更新以后 state
的值 ,第二个参数是派发更新的 dispatch
函数。
咱们来看一下useReducer
如何使用:
const DemoUseReducer = ()=>{
/* number为更新后的state值, dispatchNumbner 为当前的派发函数 */
const [ number , dispatchNumbner ] = useReducer((state,action)=>{
const { payload , name } = action
/* return的值为新的state */
switch(name){
case 'add':
return state + 1
case 'sub':
return state - 1
case 'reset':
return payload
}
return state
},0)
return <div> 当前值:{ number } { /* 派发更新 */ } <button onClick={()=>dispatchNumbner({ name:'add' })} >增长</button> <button onClick={()=>dispatchNumbner({ name:'sub' })} >减小</button> <button onClick={()=>dispatchNumbner({ name:'reset' ,payload:666 })} >赋值</button> { /* 把dispatch 和 state 传递给子组件 */ } <MyChildren dispatch={ dispatchNumbner } State={{ number }} /> </div>
}
复制代码
咱们可使用 useContext
,来获取父级组件传递过来的 context
值,这个当前值就是最近的父级组件 Provider
设置的 value
值,useContext
参数通常是由 createContext
方式引入 ,也能够父级上下文 context
传递 ( 参数为 context
)。useContext
能够代替 context.Consumer
来获取 Provider
中保存的 value
值
/* 用useContext方式 */
const DemoContext = ()=> {
const value:any = useContext(Context)
/* my name is alien */
return <div> my name is { value.name }</div>
}
/* 用Context.Consumer 方式 */
const DemoContext1 = ()=>{
return <Context.Consumer> {/* my name is alien */} { (value)=> <div> my name is { value.name }</div> } </Context.Consumer>
}
export default ()=>{
return <div> <Context.Provider value={{ name:'alien' , age:18 }} > <DemoContext /> <DemoContext1 /> </Context.Provider> </div>
}
复制代码
useImperativeHandle
能够配合 forwardRef
自定义暴露给父组件的实例值。这个颇有用,咱们知道,对于子组件,若是是class
类组件,咱们能够经过ref
获取类组件的实例,可是在子组件是函数组件的状况,若是咱们不能直接经过ref
的,那么此时useImperativeHandle
和 forwardRef
配合就能达到效果。
useImperativeHandle
接受三个参数:
第一个参数ref: 接受 forWardRef
传递过来的 ref
。
第二个参数 createHandle
:处理函数,返回值做为暴露给父组件的ref
对象。
第三个参数 deps
:依赖项 deps
,依赖项更改造成新的ref
对象。
咱们来模拟给场景,用useImperativeHandle
,使得父组件能让子组件中的input
自动赋值并聚焦。
function Son (props,ref) {
console.log(props)
const inputRef = useRef(null)
const [ inputValue , setInputValue ] = useState('')
useImperativeHandle(ref,()=>{
const handleRefs = {
/* 声明方法用于聚焦input框 */
onFocus(){
inputRef.current.focus()
},
/* 声明方法用于改变input的值 */
onChangeValue(value){
setInputValue(value)
}
}
return handleRefs
},[])
return <div> <input placeholder="请输入内容" ref={inputRef} value={inputValue} /> </div>
}
const ForwarSon = forwardRef(Son)
class Index extends React.Component{
inputRef = null
handerClick(){
const { onFocus , onChangeValue } =this.cur
onFocus()
onChangeValue('let us learn React!')
}
render(){
return <div style={{ marginTop:'50px' }} > <ForwarSon ref={node => (this.inputRef = node)} /> <button onClick={this.handerClick.bind(this)} >操控子组件</button> </div>
}
}
复制代码
效果:
useDebugValue
可用于在 React
开发者工具中显示自定义 hook
的标签。这个hooks
目的就是检查自定义hooks
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
复制代码
咱们不推荐你向每一个自定义 Hook 添加 debug 值。当它做为共享库的一部分时才最有价值。在某些状况下,格式化值的显示多是一项开销很大的操做。除非须要检查 Hook,不然没有必要这么作。所以,useDebugValue 接受一个格式化函数做为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值做为参数,而且会返回一个格式化的显示值。
useTransition
容许延时由state
改变而带来的视图渲染。避免没必要要的渲染。它还容许组件将速度较慢的数据获取更新推迟到随后渲染,以便可以当即渲染更重要的更新。
const TIMEOUT_MS = { timeoutMs: 2000 }
const [startTransition, isPending] = useTransition(TIMEOUT_MS)
复制代码
useTransition
接受一个对象, timeoutMs
代码须要延时的时间。
返回一个数组。第一个参数: 是一个接受回调的函数。咱们用它来告诉 React
须要推迟的 state
。 第二个参数: 一个布尔值。表示是否正在等待,过分状态的完成(延时state
的更新)。
下面咱们引入官网的列子,来了解useTransition
的使用。
const SUSPENSE_CONFIG = { timeoutMs: 2000 };
function App() {
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
return (
<> <button disabled={isPending} onClick={() => { startTransition(() => { const nextUserId = getNextId(resource.userId); setResource(fetchProfileData(nextUserId)); }); }} > Next </button> {isPending ? " 加载中..." : null} <Suspense fallback={<Spinner />}> <ProfilePage resource={resource} /> </Suspense> </>
);
}
复制代码
在这段代码中,咱们使用 startTransition
包装了咱们的数据获取。这使咱们能够当即开始获取用户资料的数据,同时推迟下一个用户资料页面以及其关联的 Spinner
的渲染 2 秒钟( timeoutMs
中显示的时间)。
这个api
目前处于实验阶段,没有被彻底开放出来。
接下来,咱们来一块儿研究react-dom
中比较重要的api
。
render
是咱们最经常使用的react-dom
的 api
,用于渲染一个react
元素,通常react
项目咱们都用它,渲染根部容器app
。
ReactDOM.render(element, container[, callback])
复制代码
使用
ReactDOM.render(
< App / >,
document.getElementById('app')
)
复制代码
ReactDOM.render
会控制container
容器节点里的内容,可是不会修改容器节点自己。
服务端渲染用hydrate
。用法与 render()
相同,但它用于在 ReactDOMServer
渲染的容器中对 HTML
的内容进行 hydrate
操做。
ReactDOM.hydrate(element, container[, callback])
复制代码
Portal
提供了一种将子节点渲染到存在于父组件之外的 DOM
节点的优秀的方案。createPortal
能够把当前组件或 element
元素的子节点,渲染到组件以外的其余地方。
那么具体应用到什么场景呢?
好比一些全局的弹窗组件model
,<Model/>
组件通常都写在咱们的组件内部,却是真正挂载的dom
,都是在外层容器,好比body
上。此时就很适合createPortal
API。
createPortal
接受两个参数:
ReactDOM.createPortal(child, container)
复制代码
第一个: child
是任何可渲染的 React
子元素 第二个: container
是一个 DOM
元素。
接下来咱们实践一下:
function WrapComponent({ children }){
const domRef = useRef(null)
const [ PortalComponent, setPortalComponent ] = useState(null)
React.useEffect(()=>{
setPortalComponent( ReactDOM.createPortal(children,domRef.current) )
},[])
return <div> <div className="container" ref={ domRef } ></div> { PortalComponent } </div>
}
class Index extends React.Component{
render(){
return <div style={{ marginTop:'50px' }} > <WrapComponent> <div >hello,world</div> </WrapComponent> </div>
}
}
复制代码
效果
咱们能够看到,咱们children
实际在container
以外挂载的,可是已经被createPortal
渲染到container
中。
在react-legacy
模式下,对于事件,react
事件有批量更新来处理功能,可是这一些很是规的事件中,批量更新功能会被打破。因此咱们能够用react-dom
中提供的unstable_batchedUpdates
来进行批量更新。
一次点击实现的批量更新
class Index extends React.Component{
constructor(props){
super(props)
this.state={
numer:1,
}
}
handerClick=()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
}
render(){
return <div style={{ marginTop:'50px' }} > <button onClick={ this.handerClick } >click me</button> </div>
}
}
复制代码
效果
渲染次数一次。
批量更新条件被打破
handerClick=()=>{
Promise.resolve().then(()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
})
}
复制代码
效果
渲染次数三次。
unstable_batchedUpdate助力
handerClick=()=>{
Promise.resolve().then(()=>{
ReactDOM.unstable_batchedUpdates(()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
})
})
}
复制代码
渲染次数一次,完美解决批量更新问题。
flushSync
能够将回调函数中的更新任务,放在一个较高的优先级中。咱们知道react
设定了不少不一样优先级的更新任务。若是一次更新任务在flushSync
回调函数内部,那么将得到一个较高优先级的更新。好比
ReactDOM.flushSync(()=>{
/* 这次更新将设置一个较高优先级的更新 */
this.setState({ name: 'alien' })
})
复制代码
为了让你们理解flushSync
,我这里作一个demo
奉上,
/* flushSync */
import ReactDOM from 'react-dom'
class Index extends React.Component{
state={ number:0 }
handerClick=()=>{
setTimeout(()=>{
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(()=>{
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
render(){
const { number } = this.state
console.log(number) // 打印什么??
return <div> <div>{ number }</div> <button onClick={this.handerClick} >测试flushSync</button> </div>
}
}
复制代码
先不看答案,点击一下按钮,打印什么呢?
咱们来点击一下看看
打印 0 3 4 1 ,相信不难理解为何这么打印了。
flushSync
this.setState({ number: 3 })
设定了一个高优先级的更新,因此3 先被打印相信这个demo
让咱们更深刻了解了flushSync
。
findDOMNode
用于访问组件DOM
元素节点,react
推荐使用ref
模式,不指望使用findDOMNode
。
ReactDOM.findDOMNode(component)
复制代码
注意的是:
1 findDOMNode
只能用在已经挂载的组件上。
2 若是组件渲染内容为 null
或者是 false
,那么 findDOMNode
返回值也是 null
。
3 findDOMNode
不能用于函数组件。
接下来让咱们看一下,findDOMNode
具体怎么使用的:
class Index extends React.Component{
handerFindDom=()=>{
console.log(ReactDOM.findDOMNode(this))
}
render(){
return <div style={{ marginTop:'100px' }} > <div>hello,world</div> <button onClick={ this.handerFindDom } >获取容器dom</button> </div>
}
}
复制代码
效果:
咱们彻底能够将外层容器用ref
来标记,获取捕获原生的dom
节点。
从 DOM
中卸载组件,会将其事件处理器和 state
一并清除。 若是指定容器上没有对应已挂载的组件,这个函数什么也不会作。若是组件被移除将会返回 true
,若是没有组件可被移除将会返回 false
。
咱们来简单举例看看unmountComponentAtNode
如何使用?
function Text(){
return <div>hello,world</div>
}
class Index extends React.Component{
node = null
constructor(props){
super(props)
this.state={
numer:1,
}
}
componentDidMount(){
/* 组件初始化的时候,建立一个 container 容器 */
ReactDOM.render(<Text/> , this.node )
}
handerClick=()=>{
/* 点击卸载容器 */
const state = ReactDOM.unmountComponentAtNode(this.node)
console.log(state)
}
render(){
return <div style={{ marginTop:'50px' }} > <div ref={ ( node ) => this.node = node } ></div> <button onClick={ this.handerClick } >click me</button> </div>
}
}
复制代码
效果
本文经过react
组件层面,工具层面,hooks
层面,react-dom
了解了api
的用法,但愿看完的同窗,可以对着文章中的demo
本身敲一遍,到头来会发现本身成长很多。
最后, 送人玫瑰,手留余香,以为有收获的朋友能够给笔者点赞,关注一波 ,陆续更新前端超硬核文章。
提早透漏:接下来会出一部揭秘react
事件系统的文章。
感兴趣的同窗请关注公众号 前端Sharing
持续推送优质好文
文章中,对于其余没有讲到的react-hooks
,建议你们看react-hooks
三部曲。
react-hooks三部曲
第一部: react-hooks如何使用 150+
赞👍
react进阶系列
「react进阶」年终送给react开发者的八条优化建议 918+
赞👍
「react进阶」一文吃透React高阶组件(HOC) 330+
赞👍
react源码系列
开源项目系列