从Mixin到hooks,谈谈对React16.7.0-alpha中即将引入的hooks的理解
为了实现分离业务逻辑代码,实现组件内部相关业务逻辑的复用,在React的迭代中针对类组件中的代码复用依次发布了Mixin、HOC、Render props等几个方案。此外,针对函数组件,在React v16.7.0-alpha 中提出了hooks的概念,在本身无状态的函数组件,引入独立的状态空间,也就是说在函数组件中,也可以引入类组件中的state和组件生命周期,使得函数组件变得丰富多彩起来,此外,hooks也保证了逻辑代码的复用性和独立性。
本文从针对类组件的复用解决方案开始说起,先后介绍了从Mixin、HOC到Render props的演进,最后介绍了React v16.7.0-alpha 中的 hooks以及自定义一个hooks
- Mixin
- HOC
- Render props
- React hooks的介绍以及如何自定义一个hooks
本文的原文地址发布在我的博客中:github.com/fortheallli…
欢迎star和fork~
一、Mixin
Mixin是最早出现的复用类组件中业务逻辑代码的解决方案,首先来介绍以下如何适应Mixin。下面是一个Mixin的例子:
const someMixins={printColor(){console.log(this.state.color);}setColor(newColor){this.setState({color:newColor})}componentDidMount(){..}
}复制代码
下面是一个使用Mixin的组件:
class Apple extends React.Component{//仅仅作为演示,mixins一般是通过React.createClass创建,并且ES6中没有这种写法mixins:[someMixins]constructor(props){super(props);this.state={color:'red'}this.printColor=this.printColor.bind(this);}render(){return <div className="m-box" onClick={this.printColor}>这是一个苹果</div>}
}
复制代码
在类中mixin引入公共业务逻辑:
mixins:[someMixins]
复制代码
从上面的例子,我们来总结以下mixin的缺点:
Mixin是可以存在多个的,是一个数组的形式,且Mixin中的函数是可以调用setState方法组件中的state的,因此如果有多处Mixin的模块中修改了相同的state,会无法确定state的更新来源
ES6 classes支持的是继承的模式,而不支持Mixins
Mixin会存在覆盖,比如说两个Mixin模块,存在相同生命周期函数或者相同函数名的函数,那么会存在相同函数的覆盖问题。
Mixin已经被废除,具体缺陷可以参考Mixins Considered Harmful
二、HOC
为了解决Mixin的缺陷,第二种解决方案是高阶组件(high order component,简称HOC)。
1、举例几种HOC的形式
HOC简单理解就是组件工厂,接受原始组件作为参数,添加完功能与业务后,返回新的组件。下面来介绍HOC参数的几个例子。
(1)参数仅为原始组件
const redApple = withFruit(Apple);
复制代码
(2)参数为原始组件和一个对象
const redApple = withFruit(Apple,{color:'red',weight:'200g'});
复制代码
但是这种情况比较少用,如果对象中仅仅传递的是属性,其实完全可以通过组件的props实现值的传递,我们用HOC的主要目的是分离业务,关于UI的展示,以及一些组件中的属性和状态,我们一般通过props来指定比较方便
(3)参数为原始组件和一个函数
const redApp=withFruit(App,()=>{console.log('I am a fruit')})
复制代码
(4)柯里化
最常见的是仅以一个原始组件作为参数,但是在外层包裹了业务逻辑,比如react-redux的conect函数中:
class Admin extends React.Component{}
const mapStateToProps=(state)=>{return {};
}
const mapDispatchToProps=(dispatch)=>{return {}
}const connect(mapStateToProps,mapDispatchToProps)(Admin)
复制代码
2、HOC的缺点
HOC解决了Mixin的一些缺陷,但是HOC本身也有一些缺点:
(1)难以溯源,且存在属性覆盖问题
如果原始组件A,先后通过工厂函数1,工厂函数2,工厂函数3….构造,最后生成了组件B,我们知道组件B中有很多与A组件不同的props,但是我们仅仅通过组件B,并不能知道哪个组件来自于哪个工厂函数。同时,如果有2个工厂函数同时修改了组件A的某个同名属性,那么会有属性覆盖的问题,会使得前一个工厂函数的修改结果失效。
(2)HOC是静态构建的
所谓静态构建,也就是说生成的是一个新的组件,并不会马上render,HOC组件工厂是静态构建一个组件,这类似于重新声明一个组件的部分。也就是说,HOC工厂函数里面的声明周期函数,也只有在新组件被渲染的时候才会执行。
(3)会产生无用的空组件
三、Render Prop
Render Props从名知义,也是一种剥离重复使用的逻辑代码,提升组件复用性的解决方案。在被复用的组件中,通过一个名为“render”(属性名也可以不是render,只要值是一个函数即可)的属性,该属性是一个函数,这个函数接受一个对象并返回一个子组件,会将这个函数参数中的对象作为props传入给新生成的组件。
这种方法跟直接的在父组件中,将父组件中的state直接传给子组件的区别是,通过Render Props不用写死子组件,可以动态的决定父组件需要渲染哪一个子组件。
或者再概括一点:
Render Props就是一个函数,做为一个属性被赋值给父组件,使得父组件可以根据该属性去渲染子组件。
(1)标准父子组件通信方法
首先来看常用的在类组件中常用的父子组件,父组件将自己的状态state,通过props传递给子组件。
class Son extends React.Component{render(){const {feature} = this.props;return <div><span>My hair is {feature.hair}</span><span>My nose is {feature.nose}</span></div>}
}class FatherToSon extends React.Component{constructor(){this.state = {hair:'black',nose:'high'}}render(){return <Son feature = {this.state}>}
}
复制代码
我们定义了父组件FatherToSon,存在自身的state,并且将自身的state通过props的方式传递给了子组件。
这种就是常见的利用组件的props父子间传值的方式,这个值可以是变量,对象,也可以是方法,但是仅仅使用只能一次性的给特定的子组件使用。如果现在有个Daughter组件也想复用父组件中的方法或者状态,那么必须新构建一个新组件:
class FatherToDaughter extends React.Component{constructor(){this.state = {hair:'black',nose:'high'}}render(){return <Daughter feature = {this.state}>}
}
复制代码
从上面的例子可以看出通过标准模式的父子组件的通信方法,虽然能够传递父组件的状态和函数,但是无法实现复用。
(2)Render Props的引出
我们根据Render Props的特点:
Render Props就是一个函数,做为一个属性被赋值给父组件,使得父组件可以根据该属性去渲染子组件。
重新去实现上述的(1)中的例子。
class FatherChild extends React.Component{constructor(){this.state = {hair:'black',nose:'high'}}render(){<React.Fragment>{this.props.render}</React.Fragment>}
}
复制代码
此时如果子组件要复用父组件中的属性或者函数,则可以直接使用,比如子组件Son现在可以直接调用:
<FatherChild render={(obj)=>(<Son feature={obj}>)} />
复制代码
如果子组件Daughter要复用父组件的方法,可以直接调用:
<FatherChild render={(obj)=>(<Daughter feature={obj}>)} />
复制代码
从这个例子中可以看出,通过Render Props我们实现同样实现了一个组件工厂,可以实现业务逻辑代码的复用,相比与HOC,Render Props有以下几个优点。
- 不用担心props的命名问题
- 可以溯源,子组件的props一定是来自于直接父组件
- 是动态构建的
Render Props也有一个缺点:
就是无法利用SCU这个生命周期,来实现渲染性能的优化。
四、React hooks的介绍以及如何自定义一个hooks
hooks概念在React Conf 2018被提出来,并将在未来的版本中被引入,hooks遵循函数式编程的理念,主旨是在函数组件中引入类组件中的状态和生命周期,并且这些状态和生命周期函数也可以被抽离,实现复用的同时,减少函数组件的复杂性和易用性。
hooks相关的定义还在beta中,可以在React v16.7.0-alpha中体验,为了渲染hooks定义的函数组件,必须执行React-dom的版本也为v16.7.0-alpha,引入hooks必须先安装:
npm i -s React@16.7.0-alphanpm i -s React-dom@16.7.0-alpha
复制代码
hooks主要有三部分组成,State Hooks、Effect Hooks和Custom Hooks,下面分别来一一介绍。
(1)State Hooks
跟类组件一样,这里的state就是状态的含义,将state引入到函数组件中,同时类组件中更新state的方法为setState,在State Hooks中也有相应的更新状态的方法。
function ExampleWithManyStates() {// 声明各种state以及更新相应的state的方法const [age, setAge] = useState(42);const [fruit, setFruit] = useState('banana');const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);// ...
}
复制代码
上述就声明了3个State hooks,相应的方法为useState,该方法创建一个传入初始值,创建一个state。返回一个标识该state的变量,以及更新该state的方法。
从上述例子我们来看,一个函数组件是可以通过useState创建多个state的。此外State Hooks的定义必须在函数组件的最高一级,不能在嵌套,循环等语句中使用。
function ExampleWithManyStates() {// 声明各种state以及更新相应的state的方法if(Math.random()>1){const [age, setAge] = useState(42);const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);}else{const [fruit, setFruit] = useState('banana');const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);}// ...
}
复制代码
上述的方式是不被允许的,因为一个函数组件可以存在多个State Hooks,并且useState返回的是一个数组,数组的每一个元素是没有标识信息的,完全依靠调用useState的顺序来确定哪个状态对应于哪个变量,所以必须保证使用useState在函数组件的最外层,此外后面要介绍的Effect Hooks的函数useEffect也必须在函数组件的最外层,之后会详细解释。
(2)Effect Hooks
通过State Hooks来定义组件的状态,同样通过Effect Hooks来引入生命周期,Effect hooks通过一个useEffect的方法,以一种极为简化的方式来引入生命周期。 来看一个更新的例子:
import { useState, useEffect } from 'react';function Example() {const [count, setCount] = useState(0);useEffect(() => {document.title = `You clicked ${count} times`;});return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);
}
复制代码
上述就是一个通过useEffect来实现组件中生命周期的例子,useEffect整合了componentDidMount和componentDidUpdate,也就是说在componentDidMount和componentDidUpdate的时候都会执行一遍useEffect的函数,此外为了实现componentWillUnmount这个生命周期函数,useEffect函数如果返回值是一个函数,这个函数就被定义成在componentWillUnmount这个周期内执行的函数。
useEffect(() => {//componentDidMount和componentDidUpdate周期的函数体return ()=>{ //componentWillUnmount周期的函数体}
});
复制代码
如果存在多个useState和useEffect时,必须按顺序书写,定义一个useState后,紧接着就使用一个useEffect函数。
useState('Mary')
useEffect(persistForm)
useState('Poppins')
useEffect(updateTitle)
复制代码
因此通useState一样,useEffect函数也必须位于函数组件的最高一级。
(3)Effect Hooks的补充
上述我们知道useEffect其实包含了componentDidMount和componentDidUpdate,如果我们的方法仅仅是想在componentDidMount的时候被执行,那么必须传递一个空数组作为第二个参数。
useEffect(() => {//仅在componentDidMount的时候执行
},[]);
复制代码
上述的方法会仅仅在componentDidMount,也就是函数组件第一次被渲染的时候执行,此后及时状态更新,也不会执行。
此外,为了减少不必要的状态更新和渲染,可以如下操作:
useEffect(() => {//仅在componentDidMount的时候执行
},[stateName]);
复制代码
在上述的这个例子中,只有stateName的值发生改变,才会去执行useEffect函数。
(4)Custom Hooks自定义hooks
可以将useState和useEffect的状态和生命周期函数抽离,组成一个新的函数,该函数就是一个自定义的封装完毕的hooks。
这是我写的一个hooks ---> dom-location,
可以这样引入:
npm i -s dom-location
复制代码
并且可以在函数组件中使用。这个自定义的hooks也很简单,就是封装了状态和生命周期函数。
import { useState, useEffect } from 'react'const useDomLocation = (element) => {let [elementlocation,setElementlocation] = useState(getlocation(element));useEffect(()=>{element.addEventListener('resize',handleResize);return ()=>{element.removeEventListener('resize', handleResize);}},[]);function handleResize(){setElementlocation(getlocation(element));}function getlocation(E){let rect = E.getBoundingClientRect()let top = document.documentElement.clientToplet left= document.documentElement.clientLeftreturn{top : rect.top - top,bottom : rect.bottom - top,left : rect.left - left,right : rect.right - left};}return elementlocation}
复制代码
然后直接在函数中使用:
import useDomLocation from 'dom-location';
function App() {....let obj = useDomLocation(element);}
复制代码
从Mixin到hooks,谈谈对React16.7.0-alpha中即将引入的hooks的理解相关推荐
- 谈谈基于OAuth 2.0的第三方认证 [上篇]
对于目前大部分Web应用来说,用户认证基本上都由应用自身来完成.具体来说,Web应用利用自身存储的用户凭证(基本上是用户名/密码)与用户提供的凭证进行比较进而确认其真实身份.但是这种由Web应用全权负 ...
- [html] 谈谈你对input元素中readonly和disabled属性的理解
[html] 谈谈你对input元素中readonly和disabled属性的理解 相同点:都会使文本框变成只读,不可编辑.不同点:1.disabled属性在将input文本框变成只读不可编辑的同时, ...
- 谈谈我对前端组件化中“组件”的理解,顺带写个Vue与React的demo
谈谈我对前端组件化中"组件"的理解,顺带写个Vue与React的demo 前言 前端已经过了单兵作战的时代了,现在一个稍微复杂一点的项目都需要几个人协同开发,一个战略级别的APP的 ...
- 谈谈物哀与日本文化中的生死观
谈谈物哀与日本文化中的生死观 大家在观看日本文学,动漫,电影的时候,不知道有没有发现有一个让我们国人看起来很奇怪,但是却被广泛的应用在主角身上的一个特征,主角大部分都不是双亲健在的,都是有一个或者多个 ...
- 谈谈考研刷题小程序中的卡片式设计
谈谈考研刷题小程序中的卡片式设计 大家在UI设计中爱用卡片吗?无论是b端产品还是c端产品,卡片式设计几乎无处不在,卡片已然是我们在UI设计中最常见的样式了,今天我们就来一起详细的了解一下考研题库小程序 ...
- 在 React 项目中全量使用 Hooks
目录 前言 React Hooks useState useReducer 基础用法 进阶用法 useContext useEffect useLayoutEffect useRef useImper ...
- mixin机制 vue_谈谈vue中mixin的一点理解
谈谈vue中mixin的一点理解 vue中提供了一种混合机制--mixins,用来更高效的实现组件内容的复用.最开始我一度认为这个和组件好像没啥区别..后来发现错了.下面我们来看看mixins和普通情 ...
- 如何在React Native中使用React JS Hooks?
In my articles, I'm going to be using either expo or snack online IDE and android emulator. 在我的文章中,我 ...
- react中函数式组件React Hooks
React Hooks 函数式组件 使用hooks理由 高阶组件为了复用,导致代码层级复杂 生命周期的复杂 写成functional组件,无状态组件,因为需要状态,又改成了class,成本高 在16. ...
最新文章
- C#中的委托和事件 (4)---事件和委托的编译代码
- 隐私数据在隐私AI框架中的安全流动
- R语言负二项分布函数Negative Binomial Distribution(dnbinom, pnbinom, qnbinom rnbinom )实战
- SAP云平台和SAP传统Netweaver系统互联的技术方式
- 光纤收发器的选购原则介绍
- JavaWeb学习中的小问题
- HEVC之1 HM的使用
- R 语言的学习 —— 环境准备与安装
- 深入解析C/C++的优缺点以及就业方向
- Spring.net(一)----Spring.NET框架简介及模块说明
- 学习网络编程推荐安装的软件
- nicescroll.js滚动条错位
- 计算机需要无线网卡进行网络连,电脑连接无线网络时,提示"你没有首选无线网络"怎么办...
- micropython(esp8266)SG90舵机控制
- 移动通信网中的密码算法演进之完整性保护
- 一次性修改多张图片尺寸
- 负载均衡负载场景和解决方案
- android小气泡提示,小气泡六大常见问题详解!
- 【TARS】基于TARS的调试
- H264编码器12( H264基本原理 --图文表达很清晰)
热门文章
- 创造黑科技,守护新未来 | 360公司2019年春季校园招聘正式启动!
- Spring boot - 整合 Redis缓存(上)
- 使用Maven 创建web3.0项目
- Android ActionBar高级自定义——设置标题居中和添加控件
- spring@Autowired的对象为null,非容器中的类如何调用容器中的类
- 云函数与函数式编程思想结合会产生什么?
- Redis进阶-缓存问题
- Python可变参数、关键字参数及命名关键字参数
- JAVA无法加载此类文件,ORA-00376: 此时无法读取文件问题处理
- Redis数据结构之集合