React 代码共享最佳实践方式

为了使用mixin,需要在组件中加入mixins属性,然后把我们写好的mixin包裹成一个数组,将它作为mixins的属性值:

const ComponentOne = React.createClass({

mixins: [DefaultNameMixin]

render: function () {

return 

Hello {this.props.name}

}

})

写好的mixin可以在其他组件里重复使用。

由于mixins属性值是一个数组,意味着我们可以同一个组件里调用多个mixin。在上述例子中稍作更改得到:

const DefaultFriendMixin = {

getDefaultProps: function () {

return {

friend: “Yummy”

}

}

}

const ComponentOne = React.createClass({

mixins: [DefaultNameMixin, DefaultFriendMixin]

render: function () {

return (

Hello {this.props.name}

This is my friend {this.props.friend}

)

}

})

我们甚至可以在一个mixin里包含其他的mixin

比如写一个新的mixin``DefaultProps包含以上的DefaultNameMixinDefaultFriendMixin

const DefaultPropsMixin = {

mixins: [DefaultNameMixin, DefaultFriendMixin]

}

const ComponentOne = React.createClass({

mixins: [DefaultPropsMixin]

render: function () {

return (

Hello {this.props.name}

This is my friend {this.props.friend}

)

}

})

至此,我们可以总结出mixin至少拥有以下优势:

  • 可以在多个组件里使用相同的mixin

  • 可以在同一个组件里使用多个mixin

  • 可以在同一个mixin里嵌套多个mixin

但是在不同场景下,优势也可能变成劣势:

  • 破坏原有组件的封装,可能需要去维护新的stateprops等状态

  • 不同mixin里的命名不可知,非常容易发生冲突

  • 可能产生递归调用问题,增加了项目复杂性和维护难度

除此之外,mixin在状态冲突、方法冲突、多个生命周期方法的调用顺序等问题拥有自己的处理逻辑。感兴趣的同学可以参考一下以下文章:

  • React Mixin 的使用[1]

  • Mixins Considered Harmful[2]

高阶组件—


由于mixin存在上述缺陷,故React剥离了mixin,改用高阶组件来取代它。

高阶组件本质上是一个函数,它接受一个组件作为参数,返回一个新的组件

React官方在实现一些公共组件时,也用到了高阶组件,比如react-router中的withRouter,以及Redux中的connect。在这以withRouter为例。

默认情况下,必须是经过Route路由匹配渲染的组件才存在this.props、才拥有路由参数、才能使用函数式导航的写法执行this.props.history.push('/next')跳转到对应路由的页面。高阶组件中的withRouter作用是将一个没有被Route路由包裹的组件,包裹到Route里面,从而将react-router的三个对象historylocationmatch放入到该组件的props属性里,因此能实现函数式导航跳转

withRouter的实现原理:

const withRouter = (Component) => {

const displayName = withRouter(${Component.displayName || Component.name})

const C = props => {

const { wrappedComponentRef, …remainingProps } = props

return (

<RouterContext.Consumer>

{context => {

invariant(

context,

You should not use <${displayName} /> outside a <Router>

);

return (

<Component

{…remainingProps}

{…context}

ref={wrappedComponentRef}

/>

)

}}

</RouterContext.Consumer>

)

}

使用代码:

import React, { Component } from “react”

import { withRouter } from “react-router”

class TopHeader extends Component {

render() {

return (

导航栏

{/* 点击跳转login */}

退出

)

}

exit = () => {

// 经过withRouter高阶函数包裹,就可以使用this.props进行跳转操作

this.props.history.push(“/login”)

}

}

// 使用withRouter包裹组件,返回history,location等

export default withRouter(TopHeader)

由于高阶组件的本质是获取组件并且返回新组件的方法,所以理论上它也可以像mixin一样实现多重嵌套。

例如:

写一个赋能唱歌的高阶函数

import React, { Component } from ‘react’

const widthSinging = WrappedComponent => {

return class HOC extends Component {

constructor () {

super(…arguments)

this.singing = this.singing.bind(this)

}

singing = () => {

console.log(‘i am singing!’)

}

render() {

return 

}

}

}

写一个赋能跳舞的高阶函数

import React, { Component } from ‘react’

const widthDancing = WrappedComponent => {

return class HOC extends Component {

constructor () {

super(…arguments)

this.dancing = this.dancing.bind(this)

}

dancing = () => {

console.log(‘i am dancing!’)

}

render() {

return 

}

}

}

使用以上高阶组件

import React, { Component } from “react”

import { widthSing, widthDancing } from “hocs”

class Joy extends Component {

render() {

return 

Joy

}

}

// 给Joy赋能唱歌和跳舞的特长

export default widthSinging(withDancing(Joy))

由上可见,只需使用高阶函数进行简单的包裹,就可以把原本单纯的 Joy 变成一个既能唱歌又能跳舞的夜店小王子了!

使用 HOC 的约定

在使用HOC的时候,有一些墨守成规的约定:

  • 将不相关的 Props 传递给包装组件(传递与其具体内容无关的 props);

  • 分步组合(避免不同形式的 HOC 串联调用);

  • 包含显示的 displayName 方便调试(每个 HOC 都应该符合规则的显示名称);

  • 不要在render函数中使用高阶组件(每次 render,高阶都返回新组件,影响 diff 性能);

  • 静态方法必须被拷贝(经过高阶返回的新组件,并不会包含原始组件的静态方法);

  • 避免使用 ref(ref 不会被传递);

HOC 的优缺点

至此我们可以总结一下高阶组件(HOC)的优点:

  • HOC是一个纯函数,便于使用和维护;

  • 同样由于HOC是一个纯函数,支持传入多个参数,增强其适用范围;

  • HOC返回的是一个组件,可组合嵌套,灵活性强;

当然HOC也会存在一些问题:

  • 当多个HOC嵌套使用时,无法直接判断子组件的props是从哪个HOC负责传递的;

  • 当父子组件有同名props,会导致父组件覆盖子组件同名props的问题,且react不会报错,开发者感知性低;

  • 每一个HOC都返回一个新组件,从而产生了很多无用组件,同时加深了组件层级,不便于排查问题;

修饰器高阶组件属于同一模式,在此不展开讨论。

Render Props—


Render Props是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.

这是React官方对于Render Props的定义,翻译成大白话即:“Render Props是实现React Components之间代码共享的一种技术,组件的props里边包含有一个function类型的属性,组件可以调用该props属性来实现组件内部渲染逻辑”。

官方示例:

<DataProvider render={(data) => 

Hello {data.target}

} />

如上,DataProvider组件拥有一个叫做render(也可以叫做其他名字)的props属性,该属性是一个函数,并且这个函数返回了一个React Element,在组件内部通过调用该函数来完成渲染,那么这个组件就用到了render props技术。

读者或许会疑惑,“我们为什么需要调用props属性来实现组件内部渲染,而不直接在组件内完成渲染”?借用React官方的答复,render props并非每个React开发者需要去掌握的技能,甚至你或许永远都不会用到这个方法,但它的存在的确为开发者在思考组件代码共享的问题时,提供了多一种选择。

Render Props使用场景

我们在项目开发中可能需要频繁的用到弹窗,弹窗 UI 可以千变万化,但是功能却是类似的,即打开关闭。以antd为例:

import { Modal, Button } from “antd”

class App extends React.Component {

state = { visible: false }

// 控制弹窗显示隐藏

toggleModal = (visible) => {

this.setState({ visible })

};

handleOk = (e) => {

// 做点什么

this.setState({ visible: false })

}

render() {

const { visible } = this.state

return (

<Modal

title=“Basic Modal”

visible={visible}

onOk={this.handleOk}

onCancel={this.toggleModal.bind(this, false)}

Some contents...

)

}

}

以上是最简单的Model使用实例,即便是简单的使用,我们仍需要关注它的显示状态,实现它的切换方法。但是开发者其实只想关注与业务逻辑相关的onOk,理想的使用方式应该是这样的:

Open

Some contents...

可以通过render props实现以上使用方式:

import { Modal, Button } from “antd”

class MyModal extends React.Component {

state = { on: false }

toggle = () => {

this.setState({

on: !this.state.on

})

}

renderButton = (props) => <Button {…props} onClick={this.toggle} />

renderModal = ({ onOK, …rest }) => (

<Modal

{…rest}

visible={this.state.on}

onOk={() => {

onOK && onOK()

this.toggle()

}}

onCancel={this.toggle}

/>

)

render() {

return this.props.children({

Button: this.renderButton,

Modal: this.renderModal

})

}

}

这样我们就完成了一个具备状态和基础功能的Modal,我们在其他页面使用该Modal时,只需要关注特定的业务逻辑即可。

以上可以看出,render props是一个真正的React组件,而不是像HOC一样只是一个可以返回组件的函数,这也意味着使用render props不会像HOC一样产生组件层级嵌套的问题,也不用担心props命名冲突产生的覆盖问题。

render props使用限制

render props中应该避免使用箭头函数,因为这会造成性能影响。

比如:

// 不好的示例

class MouseTracker extends React.Component {

render() {

return (

<Mouse render={mouse => (

)}/>

)

}

}

这样写是不好的,因为render方法是有可能多次渲染的,使用箭头函数,会导致每次渲染的时候,传入render的值都会不一样,而实际上并没有差别,这样会导致性能问题。

所以更好的写法应该是将传入render里的函数定义为实例方法,这样即便我们多次渲染,但是绑定的始终是同一个函数。

// 好的示例

class MouseTracker extends React.Component {

renderCat(mouse) {

return 

}

render() {

return (

)

}

}

render props的优缺点

  • 优点

    • props 命名可修改,不存在相互覆盖;
  • 清楚 props 来源;

  • 不会出现组件多层嵌套;

  • 缺点

    • 写法繁琐;
  • 无法在return语句外访问数据;

这里分享一份由字节前端面试官整理的「2021大厂前端面试手册」,内容囊括Html、CSS、Javascript、Vue、HTTP、浏览器面试题、数据结构与算法。全部整理在下方文档中,共计111道

HTML

  • HTML5有哪些新特性?

  • Doctype作⽤? 严格模式与混杂模式如何区分?它们有何意义?

  • 如何实现浏览器内多个标签页之间的通信?

  • ⾏内元素有哪些?块级元素有哪些? 空(void)元素有那些?⾏内元 素和块级元素有什么区别?

  • 简述⼀下src与href的区别?

  • cookies,sessionStorage,localStorage 的区别?

  • HTML5 的离线储存的使用和原理?

  • 怎样处理 移动端 1px 被 渲染成 2px 问题?

  • iframe 的优缺点?

  • Canvas 和 SVG 图形的区别是什么?

JavaScript

  • 问:0.1 + 0.2 === 0.3 嘛?为什么?

  • JS 数据类型

  • 写代码:实现函数能够深度克隆基本类型

  • 事件流

  • 事件是如何实现的?

  • new 一个函数发生了什么

  • 什么是作用域?

  • JS 隐式转换,显示转换

  • 了解 this 嘛,bind,call,apply 具体指什么

  • 手写 bind、apply、call

  • setTimeout(fn, 0)多久才执行,Event Loop

  • 手写题:Promise 原理

  • 说一下原型链和原型链的继承吧

  • 数组能够调用的函数有那些?

  • PWA使用过吗?serviceWorker的使用原理是啥?

  • ES6 之前使用 prototype 实现继承

  • 箭头函数和普通函数有啥区别?箭头函数能当构造函数吗?

  • 事件循环机制 (Event Loop)

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值