前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >美团前端二面经典react面试题总结_2023-03-01

美团前端二面经典react面试题总结_2023-03-01

原创
作者头像
用户10376779
发布2023-03-01 16:19:17
1.5K0
发布2023-03-01 16:19:17
举报
文章被收录于专栏:前端面试12

React 事件机制

代码语言:javascript
复制
<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是什么

  1. JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
  2. 它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;
  3. 它不同于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的子组件 接收Reduxstore作为props,通过context对象传递给子孙组件上的connect

connect做了些什么。它真正连接 ReduxReact,它包在我们的容器组件的外一层,它接收上面 Provider 提供的 store 里面的statedispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件

  • connect是一个高阶函数,首先传入mapStateToPropsmapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,

该组件具有如下特点

  • 通过props.store获取祖先Componentstore props包括statePropsdispatchPropsparentProps,合并在一起得到nextState,作为props传给真正的Component componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
  • shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState componentWillUnmount时移除注册的事件this.handleChange

由于connect的源码过长,我们只看主要逻辑

代码语言:javascript
复制
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的值,就要获取组件状态值。

代码语言:javascript
复制
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元素。

代码语言:javascript
复制
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 向子组件传递需要的信息。

代码语言:javascript
复制
// 子组件: Child
const Child = props =>{
  return <p>{props.name}</p>
}
// 父组件 Parent
const Parent = ()=>{
    return <Child name="react"></Child>
}

子组件向父组件通信:: props+回调的方式。

代码语言:javascript
复制
// 子组件: Child
const Child = props =>{
  const cb = msg =>{
      return ()=>{
          props.callback(msg)
      }
  }
  return (
      <button onClick={cb("你好!")}>你好</button>
  )
}
// 父组件 Parent
class 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自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。

代码语言:javascript
复制
// 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)具体应用例子

  • 权限控制: 利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别和 页面元素级别
代码语言:javascript
复制
// 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);
  • 组件渲染性能追踪: 借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录∶
代码语言:javascript
复制
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 组件)的渲染时间。

  • 页面复用
代码语言:javascript
复制
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);

在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,这个返回值是必须的,所以尽量将其写到函数的末尾:

代码语言:javascript
复制
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标签默认事件禁掉之后做了什么才实现了跳转?

代码语言:javascript
复制
let domArr = document.getElementsByTagName('a')
[...domArr].forEach(item=>{
    item.addEventListener('click',function () {
        location.href = this.href
    })
})

说说你用react有什么坑点?

1. JSX做表达式判断时候,需要强转为boolean类型

如果不使用 !!b 进行强转数据类型,会在页面里面输出 0

代码语言:javascript
复制
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的方法就是触发actionaction是一个用于描述以发生时间的普通对象

  • 数据改变只能通过纯函数来执行

使用纯函数来执行修改,为了描述action如何改变state的,你需要编写reducers

Redux源码

代码语言:javascript
复制
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): 返回出一个组件,它基于被包裹组件进行 功能增强;
  • 默认参数: 可以为组件包裹一层默认参数;
代码语言:javascript
复制
function proxyHoc(Comp) {
    return class extends React.Component {
        render() {
            const newProps = {
                name: 'tayde',
                age: 1,
            }
            return <Comp {...this.props} {...newProps} />
        }
    }
}
  1. 提取状态: 可以通过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件:
代码语言:javascript
复制
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 组件转化成受控组件。

代码语言:javascript
复制
const NameInput = props => (<input name="name" {...props} />)
export default withOnChange(NameInput)

包裹组件: 可以为被包裹元素进行一层包装,

代码语言:javascript
复制
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): 返回出一个组件,继承于被包裹组件,常用于以下操作

代码语言:javascript
复制
function IIHoc(Comp) {
    return class extends Comp {
        render() {
            return super.render();
        }
    };
}

渲染劫持 (Render Highjacking)

条件渲染: 根据条件,渲染不同的组件

代码语言:javascript
复制
function withLoading(Comp) {
    return class extends Comp {
        render() {
            if(this.props.isLoading) {
                return <Loading />
            } else {
                return super.render()
            }
        }
    };
}

可以直接修改被包裹组件渲染出的 React 元素树

操作状态 (Operate State) : 可以直接通过 this.state 获取到被包裹组件的状态,并进行操作。但这样的操作容易使 state 变得难以追踪,不易维护,谨慎使用。

应用场景:

权限控制,通过抽象逻辑,统一对页面进行权限判断,按不同的条件进行页面渲染:

代码语言:javascript
复制
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>);
            }
        }
    };
}

性能监控 ,包裹组件的生命周期,进行统一埋点:

代码语言:javascript
复制
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,而且之前的状态会丢失;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • React 事件机制
  • 在React中页面重新加载时怎样保留数据?
  • jsx的本质是什么?
  • connect原理
  • redux有什么缺点
  • 调用 setState 之后发生了什么
    • 约束性组件( controlled component)与非约束性组件( uncontrolled component)有什么区别?
      • 父子组件的通信方式?
        • React如何获取组件对应的DOM元素?
          • React 高阶组件是什么,和普通组件有什么区别,适用什么场景
            • 在React中组件的props改变时更新组件的有哪些方法?
            • componentWillReceiveProps调用时机
              • Component, Element, Instance 之间有什么区别和联系?
                • React的事件和普通的HTML事件有什么不同?
                  • react-router 里的 Link 标签和 a 标签的区别
                  • 说说你用react有什么坑点?
                    • shouldComponentUpdate有什么用?为什么它很重要?
                      • react16版本的reconciliation阶段和commit阶段是什么
                      • Redux实现原理解析
                      • HOC(高阶组件)
                      相关产品与服务
                      对象存储
                      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档