关于前端:美团前端二面经典react面试题总结

43次阅读

共计 15919 个字符,预计需要花费 40 分钟才能阅读完成。

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 是什么

  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 的源码过长,咱们只看次要逻辑

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 向子组件传递须要的信息。

// 子组件: Child
const Child = props =>{return <p>{props.name}</p>
}
// 父组件 Parent
const Parent = ()=>{return <Child name="react"></Child>}

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

// 子组件: 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 本身的组合性质必然产生的。咱们将它们称为纯组件,因为它们能够承受任何动静提供的子组件,但它们不会批改或复制其输出组件中的任何行为。

// 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.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);

在 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 的办法就是触发 actionaction 是一个用于形容以产生工夫的一般对象

  • 数据扭转只能通过纯函数来执行

应用纯函数来执行批改,为了形容 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} />
        }
    }
}
  1. 提取状态: 能够通过 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,而且之前的状态会失落;

正文完
 0