React 事件机制
<div onClick={this.handleClick.bind(this)}>点我</div>
React并不是将click事件绑定到了div的实在DOM上,而是在document处监听了所有的事件,当事件产生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的形式不仅仅缩小了内存的耗费,还能在组件挂在销毁时对立订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react本人实现的合成事件(SyntheticEvent)。因而如果不想要是事件冒泡的话应该调用event.preventDefault()办法,而不是调用event.stopProppagation()办法。 JSX 上写的事件并没有绑定在对应的实在 DOM 上,而是通过事件代理的形式,将所有的事件都对立绑定在了 document
上。这样的形式不仅缩小了内存耗费,还能在组件挂载销毁时对立订阅和移除事件。
另外冒泡到 document
上的事件也不是原生浏览器事件,而是 React 本人实现的合成事件(SyntheticEvent)。因而咱们如果不想要事件冒泡的话,调用 event.stopPropagation
是有效的,而应该调用 event.preventDefault
。
实现合成事件的目标如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创立一个事件对象。如果你有很多的事件监听,那么就须要调配很多的事件对象,造成高额的内存调配问题。然而对于合成事件来说,有一个事件池专门来治理它们的创立和销毁,当事件须要被应用时,就会从池子中复用对象,事件回调完结后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
在React中页面从新加载时怎么保留数据?
这个问题就设计到了数据长久化, 次要的实现形式有以下几种:
- Redux: 将页面的数据存储在redux中,在从新加载页面时,获取Redux中的数据;
- data.js: 应用webpack构建的我的项目,能够建一个文件,data.js,将数据保留data.js中,跳转页面后获取;
- sessionStorge: 在进入抉择地址页面之前,componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则阐明数据是初始化的状态。返回或进入除了抉择地址以外的页面,清掉存储的sessionStorage,保障下次进入是初始化的数据
- history API: History API 的
pushState
函数能够给历史记录关联一个任意的可序列化state
,所以能够在路由push
的时候将以后页面的一些信息存到state
中,下次返回到这个页面的时候就能从state
外面取出来到前的数据从新渲染。react-router 间接能够反对。这个办法适宜一些须要长期存储的场景。
jsx的实质是什么?
首先理解下jsx是什么
- JSX是一种JavaScript的语法扩大(eXtension),也在很多中央称之为JavaScript XML,因为看起就是一段XML语法;
- 它用于形容咱们的UI界面,并且其实现能够和JavaScript交融在一起应用;
- 它不同于Vue中的模块语法,你不须要专门学习模块语法中的一些指令(比方v-for、v-if、v-else、v-bind)。
JSX其实是嵌入到JavaScript中的一种构造语法。
实际上,jsx仅仅只是React.createElement(component, props, ...children)函数的语法糖。所有的jsx最终都会被转换成React.createElement的函数调用。
createElement须要传递三个参数
参数一:type
- 以后ReactElement的类型;
- 如果是标签元素,那么就应用字符串示意 “div”;
- 如果是组件元素,那么就间接应用组件的名称;
参数二:config
- 所有jsx中的属性都在config中以对象的属性和值的模式存储
参数三:children
- 寄存在标签中的内容,以children数组的形式进行存储;
- 当然,如果是多个元素呢?React外部有对它们进行解决,解决的源码在下方
connect原理
- 首先
connect
之所以会胜利,是因为Provider
组件: - 在原利用组件上包裹一层,使原来整个利用成为
Provider
的子组件 接管Redux
的store
作为props
,通过context
对象传递给子孙组件上的connect
connect
做了些什么。它真正连贯Redux
和React
,它包在咱们的容器组件的外一层,它接管下面Provider
提供的store
外面的state
和dispatch
,传给一个构造函数,返回一个对象,以属性模式传给咱们的容器组件
connect
是一个高阶函数,首先传入mapStateToProps
、mapDispatchToProps
,而后返回一个生产Component
的函数(wrapWithConnect
),而后再将真正的Component
作为参数传入wrapWithConnect
,这样就生产出一个通过包裹的Connect
组件,
该组件具备如下特点
- 通过
props.store
获取先人Component
的store props
包含stateProps
、dispatchProps
、parentProps
,合并在一起失去nextState
,作为props
传给真正的Component componentDidMount
时,增加事件this.store.subscribe(this.handleChange)
,实现页面交互 shouldComponentUpdate
时判断是否有防止进行渲染,晋升页面性能,并失去nextState
componentWillUnmount
时移除注册的事件this.handleChange
因为connect
的源码过长,咱们只看次要逻辑
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { return function wrapWithConnect(WrappedComponent) { class Connect extends Component { constructor(props, context) { // 从先人Component处取得store this.store = props.store || context.store this.stateProps = computeStateProps(this.store, props) this.dispatchProps = computeDispatchProps(this.store, props) this.state = { storeState: null } // 对stateProps、dispatchProps、parentProps进行合并 this.updateState() } shouldComponentUpdate(nextProps, nextState) { // 进行判断,当数据产生扭转时,Component从新渲染 if (propsChanged || mapStateProducedChange || dispatchPropsChanged) { this.updateState(nextProps) return true } } componentDidMount() { // 扭转Component的state this.store.subscribe(() = { this.setState({ storeState: this.store.getState() }) }) } render() { // 生成包裹组件Connect return ( <WrappedComponent {...this.nextState} /> ) } } Connect.contextTypes = { store: storeShape } return Connect; } }
redux有什么毛病
- 一个组件所须要的数据,必须由父组件传过来,而不能像
flux
中间接从store
取。 - 当一个组件相干数据更新时,即便父组件不须要用到这个组件,父组件还是会从新
render
,可能会有效率影响,或者须要写简单的shouldComponentUpdate
进行判断。
调用 setState 之后产生了什么
在代码中调用 setState 函数之后,React 会将传入的参数与之前的状态进行合并,而后触发所谓的和谐过程(Reconciliation)。通过和谐过程,React 会以绝对高效的形式依据新的状态构建 React 元素树并且着手从新渲染整个 UI 界面。在 React 失去元素树之后,React 会计算出新的树和老的树之间的差别,而后依据差别对界面进行最小化从新渲染。通过 diff 算法,React 可能准确制导哪些地位产生了扭转以及应该如何扭转,这就保障了按需更新,而不是全副从新渲染。
- 在 setState 的时候,React 会为以后节点创立一个 updateQueue 的更新列队。
- 而后会触发 reconciliation 过程,在这个过程中,会应用名为 Fiber 的调度算法,开始生成新的 Fiber 树, Fiber 算法的最大特点是能够做到异步可中断的执行。
- 而后 React Scheduler 会依据优先级高下,先执行优先级高的节点,具体是执行 doWork 办法。
- 在 doWork 办法中,React 会执行一遍 updateQueue 中的办法,以取得新的节点。而后比照新旧节点,为老节点打上 更新、插入、替换 等 Tag。
- 以后节点 doWork 实现后,会执行 performUnitOfWork 办法取得新节点,而后再反复下面的过程。
- 当所有节点都 doWork 实现后,会触发 commitRoot 办法,React 进入 commit 阶段。
- 在 commit 阶段中,React 会依据后面为各个节点打的 Tag,一次性更新整个 dom 元素
参考 前端进阶面试题具体解答
约束性组件( controlled component)与非约束性组件( uncontrolled component)有什么区别?
在 React中,组件负责管制和治理本人的状态。
如果将HTML中的表单元素( input、 select、 textarea等)增加到组件中,当用户与表单产生交互时,就波及表单数据存储问题。依据表单数据的存储地位,将组件分成约東性组件和非约東性组件。
约束性组件( controlled component)就是由 React管制的组件,也就是说,表单元素的数据存储在组件外部的状态中,表单到底出现什么由组件决定。
如下所示, username没有存储在DOM元素内,而是存储在组件的状态中。每次要更新 username时,就要调用 setState更新状态;每次要获取 username的值,就要获取组件状态值。
class App extends Component { //初始化状态 constructor(props) { super(props); this.state = { username: "有课前端网", }; } //查看后果 showResult() { //获取数据就是获取状态值 console.log(this.state.username); } changeUsername(e) { //原生办法获取 var value = e.target.value; //更新前,能够进行脏值检测 //更新状态 this.setState({ username: value, }); } //渲染组件 render() { //返回虚构DOM return ( <div> <p> {/*输入框绑定va1ue*/} <input type="text" onChange={this.changeUsername.bind(this)} value={this.state.username} /> </p> <p> <button onClick={this.showResult.bind(this)}>查看后果</button> </p> </div> ); }}
非约束性组件( uncontrolled component)就是指表单元素的数据交由元素本身存储并解决,而不是通过 React组件。表单如何出现由表单元素本身决定。
如下所示,表单的值并没有存储在组件的状态中,而是存储在表单元素中,当要批改表单数据时,间接输出表单即可。有时也能够获取元素,再手动批改它的值。当要获取表单数据时,要首先获取表单元素,而后通过表单元素获取元素的值。
留神:为了不便在组件中获取表单元素,通常为元素设置ref属性,在组件外部通过refs属性获取对应的DOM元素。
class App extends Component { //查看后果 showResult() { //获取值 console.log(this.refs.username.value); //批改值,就是批改元素本身的值 this.refs.username.value = "业余前端学习平台"; //渲染组件 //返回虚构DOM return ( <div> <p> {/*非约束性组件中,表单元素通过 defaultvalue定义*/} <input type="text" ref=" username" defaultvalue="有课前端网" /> </p> <p> <button onClick={this.showResult.bind(this)}>查看后果</button> </p> </div> ); }}
尽管非约東性组件通常更容易实现,能够通过refs间接获取DOM元素,并获取其值,然而 React倡议应用约束性组件。次要起因是,约東性组件反对即时字段验证,容许有条件地禁用/启用按钮,强制输出格局等。
父子组件的通信形式?
父组件向子组件通信:父组件通过 props 向子组件传递须要的信息。
// 子组件: Childconst Child = props =>{ return <p>{props.name}</p>}// 父组件 Parentconst Parent = ()=>{ return <Child name="react"></Child>}
子组件向父组件通信:: props+回调的形式。
// 子组件: Childconst Child = props =>{ const cb = msg =>{ return ()=>{ props.callback(msg) } } return ( <button onClick={cb("你好!")}>你好</button> )}// 父组件 Parentclass Parent extends Component { callback(msg){ console.log(msg) } render(){ return <Child callback={this.callback.bind(this)}></Child> }}
React如何获取组件对应的DOM元素?
能够用ref来获取某个子节点的实例,而后通过以后class组件实例的一些特定属性来间接获取子节点实例。ref有三种实现办法:
- 字符串格局:字符串格局,这是React16版本之前用得最多的,例如:
<p ref="info">span</p>
- 函数格局:ref对应一个办法,该办法有一个参数,也就是对应的节点实例,例如:
<p ref={ele => this.info = ele}></p>
- createRef办法:React 16提供的一个API,应用React.createRef()来实现
React 高阶组件是什么,和一般组件有什么区别,实用什么场景
官网解释∶
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的设计模式。
高阶组件(HOC)就是一个函数,且该函数承受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react本身的组合性质必然产生的。咱们将它们称为纯组件,因为它们能够承受任何动静提供的子组件,但它们不会批改或复制其输出组件中的任何行为。
// hoc的定义function withSubscription(WrappedComponent, selectData) { return class extends React.Component { constructor(props) { super(props); this.state = { data: selectData(DataSource, props) }; } // 一些通用的逻辑解决 render() { // ... 并应用新数据渲染被包装的组件! return <WrappedComponent data={this.state.data} {...this.props} />; } };// 应用const BlogPostWithSubscription = withSubscription(BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id));
1)HOC的优缺点
- 长处∶ 逻辑服用、不影响被包裹组件的外部逻辑。
- 毛病∶hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被笼罩
2)实用场景
- 代码复用,逻辑形象
- 渲染劫持
- State 形象和更改
- Props 更改
3)具体利用例子
- 权限管制: 利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度:页面级别和 页面元素级别
// HOC.jsfunction withAdminAuth(WrappedComponent) { return class extends React.Component { state = { isAdmin: false, } async UNSAFE_componentWillMount() { const currentRole = await getCurrentUserRole(); this.setState({ isAdmin: currentRole === 'Admin', }); } render() { if (this.state.isAdmin) { return <WrappedComponent {...this.props} />; } else { return (<div>您没有权限查看该页面,请分割管理员!</div>); } } };}// pages/page-a.jsclass PageA extends React.Component { constructor(props) { super(props); // something here... } UNSAFE_componentWillMount() { // fetching data } render() { // render page with data }}export default withAdminAuth(PageA);// pages/page-b.jsclass PageB extends React.Component { constructor(props) { super(props); // something here... } UNSAFE_componentWillMount() { // fetching data } render() { // render page with data }}export default withAdminAuth(PageB);
- 组件渲染性能追踪: 借助父组件子组件生命周期规定捕捉子组件的生命周期,能够不便的对某个组件的渲染工夫进行记录∶
class Home extends React.Component { render() { return (<h1>Hello World.</h1>); } } function withTiming(WrappedComponent) { return class extends WrappedComponent { constructor(props) { super(props); this.start = 0; this.end = 0; } UNSAFE_componentWillMount() { super.componentWillMount && super.componentWillMount(); this.start = Date.now(); } componentDidMount() { super.componentDidMount && super.componentDidMount(); this.end = Date.now(); console.log(`${WrappedComponent.name} 组件渲染工夫为 ${this.end - this.start} ms`); } render() { return super.render(); } }; } export default withTiming(Home);
留神:withTiming 是利用 反向继承 实现的一个高阶组件,性能是计算被包裹组件(这里是 Home 组件)的渲染工夫。
- 页面复用
const withFetching = fetching => WrappedComponent => { return class extends React.Component { state = { data: [], } async UNSAFE_componentWillMount() { const data = await fetching(); this.setState({ data, }); } render() { return <WrappedComponent data={this.state.data} {...this.props} />; } }}// pages/page-a.jsexport default withFetching(fetching('science-fiction'))(MovieList);// pages/page-b.jsexport default withFetching(fetching('action'))(MovieList);// pages/page-other.jsexport default withFetching(fetching('some-other-type'))(MovieList);
在React中组件的props扭转时更新组件的有哪些办法?
在一个组件传入的props更新时从新渲染该组件罕用的办法是在componentWillReceiveProps
中将新的props更新到组件的state中(这种state被成为派生状态(Derived State)),从而实现从新渲染。React 16.3中还引入了一个新的钩子函数getDerivedStateFromProps
来专门实现这一需要。
(1)componentWillReceiveProps(已废除)
在react的componentWillReceiveProps(nextProps)生命周期中,能够在子组件的render函数执行前,通过this.props获取旧的属性,通过nextProps获取新的props,比照两次props是否雷同,从而更新子组件本人的state。
这样的益处是,能够将数据申请放在这里进行执行,须要传的参数则从componentWillReceiveProps(nextProps)中获取。而不用将所有的申请都放在父组件中。于是该申请只会在该组件渲染时才会收回,从而加重申请累赘。
(2)getDerivedStateFromProps(16.3引入)
这个生命周期函数是为了代替componentWillReceiveProps
存在的,所以在须要应用componentWillReceiveProps
时,就能够思考应用getDerivedStateFromProps
来进行代替。
两者的参数是不雷同的,而getDerivedStateFromProps
是一个动态函数,也就是这个函数不能通过this拜访到class的属性,也并不举荐间接拜访属性。而是应该通过参数提供的nextProps以及prevState来进行判断,依据新传入的props来映射到state。
须要留神的是,如果props传入的内容不须要影响到你的state,那么就须要返回一个null,这个返回值是必须的,所以尽量将其写到函数的开端:
static getDerivedStateFromProps(nextProps, prevState) { const {type} = nextProps; // 当传入的type发生变化的时候,更新state if (type !== prevState.type) { return { type, }; } // 否则,对于state不进行任何操作 return null;}
componentWillReceiveProps调用机会
- 曾经被废除掉
- 当props扭转的时候才调用,子组件第二次接管到props的时候
Component, Element, Instance 之间有什么区别和分割?
- 元素: 一个元素
element
是一个一般对象(plain object),形容了对于一个DOM节点或者其余组件component
,你想让它在屏幕上出现成什么样子。元素element
能够在它的属性props
中蕴含其余元素(译注:用于造成元素树)。创立一个React元素element
老本很低。元素element
创立之后是不可变的。 - 组件: 一个组件
component
能够通过多种形式申明。能够是带有一个render()
办法的类,简略点也能够定义为一个函数。这两种状况下,它都把属性props
作为输出,把返回的一棵元素树作为输入。 - 实例: 一个实例
instance
是你在所写的组件类component class
中应用关键字this
所指向的货色(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。
函数式组件(Functional component
)基本没有实例instance
。类组件(Class component
)有实例instance
,然而永远也不须要间接创立一个组件的实例,因为React帮咱们做了这些。
React的事件和一般的HTML事件有什么不同?
区别:
- 对于事件名称命名形式,原生事件为全小写,react 事件采纳小驼峰;
- 对于事件函数解决语法,原生事件为字符串,react 事件为函数;
- react 事件不能采纳 return false 的形式来阻止浏览器的默认行为,而必须要地明确地调用
preventDefault()
来阻止默认行为。
合成事件是 react 模仿原生 DOM 事件所有能力的一个事件对象,其长处如下:
- 兼容所有浏览器,更好的跨平台;
- 将事件对立寄存在一个数组,防止频繁的新增与删除(垃圾回收)。
- 不便 react 对立治理和事务机制。
事件的执行程序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为须要冒泡到document 上合成事件才会执行。
react-router 里的 Link 标签和 a 标签的区别
从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶ <Link>
是react-router 里实现路由跳转的链接,个别配合<Route>
应用,react-router接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link>
的“跳转”行为只会触发相匹配的<Route>
对应的页面内容更新,而不会刷新整个页面。
<Link>
做了3件事件:
- 有onclick那就执行onclick
- click的时候阻止a标签默认事件
- 依据跳转href(即是to),用history (web前端路由两种形式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面而
<a>
标签就是一般的超链接了,用于从以后页面跳转到href指向的另一 个页面(非锚点状况)。
a标签默认事件禁掉之后做了什么才实现了跳转?
let domArr = document.getElementsByTagName('a')[...domArr].forEach(item=>{ item.addEventListener('click',function () { location.href = this.href })})
说说你用react有什么坑点?
1. JSX做表达式判断时候,须要强转为boolean类型
如果不应用!!b
进行强转数据类型,会在页面外面输入0
。
render() { const b = 0; return <div> { !!b && <div>这是一段文本</div> } </div>}
2. 尽量不要在 componentWillReviceProps
里应用 setState,如果肯定要应用,那么须要判断完结条件,不然会呈现有限重渲染,导致页面解体
3. 给组件增加ref时候,尽量不要应用匿名函数,因为当组件更新的时候,匿名函数会被当做新的prop解决,让ref属性承受到新函数的时候,react外部会先清空ref,也就是会以null为回调参数先执行一次ref这个props,而后在以该组件的实例执行一次ref,所以用匿名函数做ref的时候,有的时候去ref赋值后的属性会取到null
4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入
shouldComponentUpdate有什么用?为什么它很重要?
组件状态数据或者属性数据产生更新的时候,组件会进入存在期,视图会渲染更新。在生命周期办法 should ComponentUpdate中,容许抉择退出某些组件(和它们的子组件)的和解过程。
和解的最终目标是依据新的状态,以最无效的形式更新用户界面。如果咱们晓得用户界面的某一部分不会扭转,那么没有理由让 React弄清楚它是否应该更新渲染。通过在 shouldComponentUpdate办法中返回 false, React将让以后组件及其所有子组件放弃与以后组件状态雷同。
react16版本的reconciliation阶段和commit阶段是什么
- reconciliation阶段蕴含的次要工作是对current tree 和 new tree 做diff计算,找出变动局部。进行遍历、比照等是能够中断,歇一会儿接着再来。
- commit阶段是对上一阶段获取到的变动局部利用到实在的DOM树中,是一系列的DOM操作。不仅要保护更简单的DOM状态,而且中断后再持续,会对用户体验造成影响。在广泛的利用场景下,此阶段的耗时比diff计算等耗时绝对短。
Redux实现原理解析
为什么要用redux
在React
中,数据在组件中是单向流动的,数据从一个方向父组件流向子组件(通过props
),所以,两个非父子组件之间通信就绝对麻烦,redux
的呈现就是为了解决state
外面的数据问题
Redux设计理念
Redux
是将整个利用状态存储到一个中央上称为store
,外面保留着一个状态树store tree
,组件能够派发(dispatch
)行为(action
)给store
,而不是间接告诉其余组件,组件外部通过订阅store
中的状态state
来刷新本人的视图
Redux三大准则
- 惟一数据源
整个利用的state都被存储到一个状态树外面,并且这个状态树,只存在于惟一的store中
- 放弃只读状态
state
是只读的,惟一扭转state
的办法就是触发action
,action
是一个用于形容以产生工夫的一般对象
- 数据扭转只能通过纯函数来执行
应用纯函数来执行批改,为了形容action
如何扭转state
的,你须要编写reducers
Redux源码
let createStore = (reducer) => { let state; //获取状态对象 //寄存所有的监听函数 let listeners = []; let getState = () => state; //提供一个办法供内部调用派发action let dispath = (action) => { //调用管理员reducer失去新的state state = reducer(state, action); //执行所有的监听函数 listeners.forEach((l) => l()) } //订阅状态变动事件,当状态扭转产生之后执行监听函数 let subscribe = (listener) => { listeners.push(listener); } dispath(); return { getState, dispath, subscribe }}let combineReducers=(renducers)=>{ //传入一个renducers治理组,返回的是一个renducer return function(state={},action={}){ let newState={}; for(var attr in renducers){ newState[attr]=renducers[attr](state[attr],action) } return newState; }}export {createStore,combineReducers};
HOC(高阶组件)
HOC(Higher Order Componennt) 是在 React 机制下社区造成的一种组件模式,在很多第三方开源库中体现弱小。
简述:
- 高阶组件不是组件,是 加强函数,能够输出一个元组件,返回出一个新的加强组件;
- 高阶组件的次要作用是 代码复用,操作 状态和参数;
用法:
- 属性代理 (Props Proxy): 返回出一个组件,它基于被包裹组件进行 性能加强;
- 默认参数: 能够为组件包裹一层默认参数;
function proxyHoc(Comp) { return class extends React.Component { render() { const newProps = { name: 'tayde', age: 1, } return <Comp {...this.props} {...newProps} /> } }}
- 提取状态: 能够通过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件:
function withOnChange(Comp) { return class extends React.Component { constructor(props) { super(props) this.state = { name: '', } } onChangeName = () => { this.setState({ name: 'dongdong', }) } render() { const newProps = { value: this.state.name, onChange: this.onChangeName, } return <Comp {...this.props} {...newProps} /> } }}
应用姿态如下,这样就能十分疾速的将一个 Input 组件转化成受控组件。
const NameInput = props => (<input name="name" {...props} />)export default withOnChange(NameInput)
包裹组件: 能够为被包裹元素进行一层包装,
function withMask(Comp) { return class extends React.Component { render() { return ( <div> <Comp {...this.props} /> <div style={{ width: '100%', height: '100%', backgroundColor: 'rgba(0, 0, 0, .6)', }} </div> ) } }}
反向继承 (Inheritance Inversion): 返回出一个组件,继承于被包裹组件,罕用于以下操作
function IIHoc(Comp) { return class extends Comp { render() { return super.render(); } };}
渲染劫持 (Render Highjacking)
条件渲染: 依据条件,渲染不同的组件
function withLoading(Comp) { return class extends Comp { render() { if(this.props.isLoading) { return <Loading /> } else { return super.render() } } };}
能够间接批改被包裹组件渲染出的 React 元素树
操作状态 (Operate State) : 能够间接通过 this.state 获取到被包裹组件的状态,并进行操作。但这样的操作容易使 state 变得难以追踪,不易保护,审慎应用。
利用场景:
权限管制,通过形象逻辑,对立对页面进行权限判断,按不同的条件进行页面渲染:
function withAdminAuth(WrappedComponent) { return class extends React.Component { constructor(props){ super(props) this.state = { isAdmin: false, } } async componentWillMount() { const currentRole = await getCurrentUserRole(); this.setState({ isAdmin: currentRole === 'Admin', }); } render() { if (this.state.isAdmin) { return <Comp {...this.props} />; } else { return (<div>您没有权限查看该页面,请分割管理员!</div>); } } };}
性能监控 ,包裹组件的生命周期,进行对立埋点:
function withTiming(Comp) { return class extends Comp { constructor(props) { super(props); this.start = Date.now(); this.end = 0; } componentDidMount() { super.componentDidMount && super.componentDidMount(); this.end = Date.now(); console.log(`${WrappedComponent.name} 组件渲染工夫为 ${this.end - this.start} ms`); } render() { return super.render(); } };}
代码复用,能够将反复的逻辑进行形象。
应用留神:
- 纯函数: 加强函数应为纯函数,防止侵入批改元组件;
- 防止用法净化: 现实状态下,应透传元组件的无关参数与事件,尽量保障用法不变;
- 命名空间: 为 HOC 减少特异性的组件名称,这样能便于开发调试和查找问题;
- 援用传递 : 如果须要传递元组件的 refs 援用,能够应用React.forwardRef;
静态方法 : 元组件上的静态方法并无奈被主动传出,会导致业务层无奈调用;解决:
- 函数导出
- 静态方法赋值
- 从新渲染: 因为加强函数每次调用是返回一个新组件,因而如果在 Render中应用加强函数,就会导致每次都从新渲染整个HOC,而且之前的状态会失落;