解答
如果您尝试直接改变组件的状态,React 将无法得知它需要重新渲染组件。通过使用setState()
方法,React 可以更新组件的UI。
另外,您还可以谈谈如何不保证状态更新是同步的。如果需要基于另一个状态(或属性)更新组件的状态,请向setState()
传递一个函数,该函数将 state 和 props 作为其两个参数:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
shouldComponentUpdate
钩子对新旧props
进行比较,如果值相同则阻止更新,避免不必要的渲染,或者使用PureReactComponent
替代Component
,其内部已经封装了shouldComponentUpdate
的浅比较逻辑key
属性,以方便React
的diff
算法中对该节点的复用,减少节点的创建和删除操作render
函数中减少类似onClick={() => {doSomething()}}
的写法,每次调用render函数时均会创建一个新的函数,即使内容没有发生任何变化,也会导致节点没必要的重渲染,建议将函数保存在组件的成员对象中,这样只会创建一次props
如果需要经过一系列运算后才能拿到最终结果,则可以考虑使用reselect
库对结果进行缓存,如果props值未发生变化,则结果直接从缓存中拿,避免高昂的运算代价webpack-bundle-analyzer
分析当前页面的依赖包,是否存在不合理性,如果存在,找到优化点并进行优化Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。
在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。
注意事项:
React基于Virtual DOM实现了一个SyntheticEvent层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合W3C标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。
在React底层,主要对合成事件做了两件事:
React 基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。大多数情况下,React 对 DOM 的渲染效率足以业务日常。但在个别复杂业务场景下,性能问题依然会困扰我们。此时需要采取一些措施来提升运行性能,其很重要的一个方向,就是避免不必要的渲染(Render)。这里提下优化的点:
在 React 类组件中,可以利用 shouldComponentUpdate或者 PureComponent 来减少因父组件更新而触发子组件的 render,从而达到目的。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 即可。
在函数组件中,并没有 shouldComponentUpdate 这个生命周期,可以利用高阶组件,封装一个类似 PureComponet 的功能
React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似,但不同的是, React.memo只能用于函数组件。
具体的执行过程如下(源码级解析):
setState
入口函数,入口函数在这里就是充当一个分发器的角色,根据入参的不同,将其分发到不同的功能函数中去;ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
enqueueSetState
方法将新的 state
放进组件的状态队列里,并调用 enqueueUpdate
来处理将要更新的实例对象;enqueueSetState: function (publicInstance, partialState) {
// 根据 this 拿到对应的组件实例
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 这个 queue 对应的就是一个组件实例的 state 数组
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// enqueueUpdate 用来处理当前的组件实例
enqueueUpdate(internalInstance);
}
enqueueUpdate
方法中引出了一个关键的对象——batchingStrategy
,该对象所具备的isBatchingUpdates
属性直接决定了当下是要走更新流程,还是应该排队等待;如果轮到执行,就调用 batchedUpdates
方法来直接发起更新流程。由此可以推测,batchingStrategy
或许正是 React 内部专门用于管控批量更新的对象。function enqueueUpdate(component) {
ensureInjected();
// 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段
if (!batchingStrategy.isBatchingUpdates) {
// 若当前没有处于批量创建/更新组件的阶段,则立即更新组件
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
注意:batchingStrategy
对象可以理解为“锁管理器”。这里的“锁”,是指 React 全局唯一的 isBatchingUpdates
变量,isBatchingUpdates
的初始值是 false
,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate
去执行更新动作时,会先把这个锁给“锁上”(置为 true
),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents
里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。
react的父级组件的render函数重新渲染会引起子组件的render方法的重新渲染。但是,有的时候子组件的接受父组件的数据没有变动。子组件render的执行会影响性能,这时就可以使用shouldComponentUpdate来解决这个问题。
使用方法如下:
shouldComponentUpdate(nexrProps) {
if (this.props.num === nexrProps.num) {
return false
}
return true;
}
shouldComponentUpdate提供了两个参数nextProps和nextState,表示下一次props和一次state的值,当函数返回false时候,render()方法不执行,组件也就不会渲染,返回true时,组件照常重渲染。此方法就是拿当前props中值和下一次props中的值进行对比,数据相等时,返回false,反之返回true。
需要注意,在进行新旧对比的时候,是浅对比,也就是说如果比较的数据时引用数据类型,只要数据的引用的地址没变,即使内容变了,也会被判定为true。
面对这个问题,可以使用如下方法进行解决:
(1)使用setState改变数据之前,先采用ES6中assgin进行拷贝,但是assgin只深拷贝的数据的第一层,所以说不是最完美的解决办法:
const o2 = Object.assign({},this.state.obj)
o2.student.count = '00000';
this.setState({
obj: o2,
})
(2)使用JSON.parse(JSON.stringfy())进行深拷贝,但是遇到数据为undefined和函数时就会错。
const o2 = JSON.parse(JSON.stringify(this.state.obj))
o2.student.count = '00000';
this.setState({
obj: o2,
})
在一个组件传入的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;
}
区别:
preventDefault()
来阻止默认行为。合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:
事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。
在React中,组件返回的元素只能有一个根元素。为了不添加多余的DOM节点,我们可以使用Fragment标签来包裹所有的元素,Fragment标签不会渲染出任何元素。React官方对Fragment的解释:
React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
import React, { Component, Fragment } from 'react'
// 一般形式
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
// 也可以写成以下形式
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
<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 通常将组件生命周期分为三个阶段:
挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:
组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,我们必须在构造函数中执行 super(props)
,否则无法在构造函数中拿到this。
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor。
constructor中通常只做两件事:
constructor(props) {
super(props);
// 不要在构造函数中调用 setState,可以直接给 state 设置初始值
this.state = { counter: 0 }
this.handleClick = this.handleClick.bind(this)
}
static getDerivedStateFromProps(props, state)
这是个静态方法,所以不能在这个函数里使用 this
,有两个参数 props
和 state
,分别指接收到的新参数和当前组件的 state
对象,这个函数会返回一个对象用来更新当前的 state
对象,如果不需要更新可以返回 null
。
该函数会在装载时,接收到新的 props
或者调用了 setState
和 forceUpdate
时被调用。如当接收到新的属性想修改 state
,就可以使用。
// 当 props.counter 变化时,赋值给 state
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
if (props.counter !== state.counter) {
return {
counter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
现在可以显式传入 counter
,但是这里有个问题,如果想要通过点击实现 state.counter
的增加,但这时会发现值不会发生任何变化,一直保持 props
传进来的值。这是由于在 React 16.4^ 的版本中 setState
和 forceUpdate
也会触发这个生命周期,所以当组件内部 state
变化后,就会重新走这个方法,同时会把 state
值赋值为 props
的值。因此需要多加一个字段来记录之前的 props
值,这样就会解决上述问题。具体如下:
// 这里只列出需要变化的地方
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
// 增加一个 preCounter 来记录之前的 props 传来的值
preCounter: 0,
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
// 跟 state.preCounter 进行比较
if (props.counter !== state.preCounter) {
return {
counter: props.counter,
preCounter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
render是React 中最核心的方法,一个组件中必须要有这个方法,它会根据状态 state
和属性 props
渲染组件。这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑,通常调用该方法会返回以下类型中一个:
componentDidMount()会在组件挂载后(插入 DOM 树中)立即调。该阶段通常进行以下操作:
如果在 componentDidMount
中调用 setState
,就会触发一次额外的渲染,多调用了一次 render
函数,由于它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,但是我应当避免这样使用,这样会带来一定的性能问题,尽量是在 constructor
中初始化 state
对象。
在组件装载之后,将计数数字变为1:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
componentDidMount () {
this.setState({
counter: 1
})
}
render () {
return (
<div className="counter">
counter值: { this.state.counter } </div>
)
}
}
当组件的 props
改变了,或组件内部调用了 setState/forceUpdate
,会触发更新重新渲染,这个过程可能会发生多次。这个阶段会依次调用下面这些方法:
shouldComponentUpdate(nextProps, nextState)
在说这个生命周期函数之前,来看两个问题:
this.setState({number: this.state.number})
第一个问题答案是 会 ,第二个问题如果是父组件重新渲染时,不管传入的 props 有没有变化,都会引起子组件的重新渲染。
那么有没有什么方法解决在这两个场景下不让组件重新渲染进而提升性能呢?这个时候 shouldComponentUpdate
登场了,这个生命周期函数是用来提升速度的,它是在重新渲染组件开始前触发的,默认返回 true
,可以比较 this.props
和 nextProps
,this.state
和 nextState
值是否变化,来确认返回 true 或者 false
。当返回 false
时,组件的更新过程停止,后续的 render
、componentDidUpdate
也不会被调用。
注意: 添加 shouldComponentUpdate
方法时,不建议使用深度相等检查(如使用 JSON.stringify()
),因为深比较效率很低,可能会比重新渲染组件效率还低。而且该方法维护比较困难,建议使用该方法会产生明显的性能提升时使用。
getSnapshotBeforeUpdate(prevProps, prevState)
这个方法在 render
之后,componentDidUpdate
之前调用,有两个参数 prevProps
和 prevState
,表示更新之前的 props
和 state
,这个函数必须要和 componentDidUpdate
一起使用,并且要有一个返回值,默认是 null
,这个返回值作为第三个参数传给 componentDidUpdate
。
componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。 该阶段通常进行以下操作:
componentDidUpdate(prevProps, prevState, snapshot){}
该方法有三个参数:
卸载阶段只有一个生命周期函数,componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作:
这个生命周期在一个组件被卸载和销毁之前被调用,因此你不应该再这个方法中使用 setState
,因为组件一旦被卸载,就不会再装载,也就不会重新渲染。
componentDidCatch(error, info),此生命周期在后代组件抛出错误后被调用。 它接收两个参数∶
React常见的生命周期如下: React常见生命周期的过程大致如下:
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的优缺点
2)适用场景
3)具体应用例子
// HOC.js
function 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.js
class 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.js
class 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.js
export default withFetching(fetching('science-fiction'))(MovieList);
// pages/page-b.js
export default withFetching(fetching('action'))(MovieList);
// pages/page-other.js
export default withFetching(fetching('some-other-type'))(MovieList);
在getDerivedStateFromProps中进行处理。
这个生命周期函数是为了替代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;
}
PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。
在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。
不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。
使用pureComponent的好处:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的。这是因为react自动做了一层浅比较。
state 更新流程: 这个过程当中涉及的函数:
注意:此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。应该考虑使用内置的 PureComponent 组件,而不是手动编写
shouldComponentUpdate()
props 更新流程: 相对于 state 更新,props 更新后唯一的区别是增加了对 componentWillReceiveProps 的调用。关于 componentWillReceiveProps,需要知道这些事情:
(1)有状态组件
特点:
使用场景:
总结: 类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。
(2)无状态组件 特点:
使用场景:
优点:
缺点:
总结: 组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 <Button/>
、 <Input />
等组件。
Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route>
或 <Redirect>
,它里面不能放其他元素。
假如不加 <Switch>
:
import { Route } from 'react-router-dom'
<Route path="/" component={Home}></Route>
<Route path="/login" component={Login}></Route>
Route 组件的 path 属性用于匹配路径,因为需要匹配 /
到 Home
,匹配 /login
到 Login
,所以需要两个 Route,但是不能这么写。这样写的话,当 URL 的 path 为 “/login” 时,<Route path="/" />
和<Route path="/login" />
都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch>
来做到只显示一个匹配组件:
import { Switch, Route} from 'react-router-dom'
<Switch>
<Route path="/" component={Home}></Route>
<Route path="/login" component={Login}></Route>
</Switch>
此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了exact属性,它的作用就是精确匹配路径,经常与<Switch>
联合使用。只有当 URL 和该 <Route>
的 path 属性完全一致的情况下才能匹配上:
import { Switch, Route} from 'react-router-dom'
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/login" component={Login}></Route>
</Switch>
权限控制
利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别 和 页面元素级别
// HOC.js
function withAdminAuth(WrappedComponent) {
return class extends React.Component {
state = {
isAdmin: false,
}
async 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.js
class PageA extends React.Component {
constructor(props) {
super(props);
// something here...
}
componentWillMount() {
// fetching data
}
render() {
// render page with data
}
}
export default withAdminAuth(PageA);
可能你已经发现了,高阶组件其实就是装饰器模式在 React 中的实现:通过给函数传入一个组件(函数或类)后在函数内部对该组件(函数或类)进行功能的增强(不修改传入参数的前提下),最后返回这个组件(函数或类),即允许向一个现有的组件添加新的功能,同时又不去修改该组件,属于 包装模式(Wrapper Pattern) 的一种。
什么是装饰者模式:在不改变对象自身的前提下在程序运行期间动态的给对象添加一些额外的属性或行为
可以提高代码的复用性和灵活性。
再对高阶组件进行一个小小的总结:
封装组件的原则
封装原则
1、单一原则:负责单一的页面渲染
2、多重职责:负责多重职责,获取数据,复用逻辑,页面渲染等
3、明确接受参数:必选,非必选,参数尽量设置以_开头,避免变量重复
4、可扩展:需求变动能够及时调整,不影响之前代码
5、代码逻辑清晰
6、封装的组件必须具有高性能,低耦合的特性
7、组件具有单一职责:封装业务组件或者基础组件,如果不能给这个组件起一个有意义的名字,证明这个组件承担的职责可能不够单一,需要继续抽组件,直到它可以是一个独立的组件即可
本质上来说JSX是React.createElement(component, props, ...children)
方法的语法糖。在React 17之前,如果使用了JSX,其实就是在使用React, babel
会把组件转换为 CreateElement
形式。在React 17之后,就不再需要引入,因为 babel
已经可以帮我们自动引入react。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。