最近开始学习React,记录一下心得。
React hooks是16.8.0推出的,其目的是为了替换class,HOC,render props。同时网传hooks也将终结redux,那么本文将讨论hooks究竟能不能替换掉redux,HOC,render props。
1. Hooks替代Redux。
与其说Hooks替代Redux,不如说是Context在替代Redux。Context是16.3.0推出的(之前也有,但是API不一样且基本没人用)。其实从Context开始,可以说不用redux也可以写出逻辑上漂亮的APP了。context同样可以维护一个组件树共享的state,修改state,state变化的时候更新所有用到这个state的组件。之所以网传hooks要替代redux,是因为hooks里面有一个useReducer,和redux里面的reducer是一模一样。reducer其实为了处理复杂的state而引入的概念。hooks因为有了reducer,习惯redux的人在不用redux的情况下也可以用reducer的方式维护state了。
吐槽一下reducer这个名字,这根本就是乱起名字,它和js里面的array的reduce方法,或者java里面的stream的reduce一毛钱关系都没有。所以不要纠结为啥叫reducer,我们可以理解为老外的脑回路比较独特。
context+hooks完全可以替代redux的核心功能(全局state,reducer,connect),至于核心功能之外的一些功能,who care?只有那么一直在用redux的才去关心。redux轻易不会死,因为用的人很多,并且context+hooks并没有压倒性的优势去替代它。如果你是新手,那么你完全可以不去学redux了,因为context+hooks更易学,优点更多一些,虽然不是压倒性的。
谈谈redux的缺点。
1. 学习难度高。可以说学习redux和学习react本身难度基本相当。个人认为redux能活下来简直是个奇迹,难道是因为react16.3.0之前其没有同行,或者同行更差劲?
而context+hooks就好学的多了,对吧?
2. 采用全局state,性能差。redux通常是一个页面只维护一个state,这带来两点问题。其一是因为state变化之后每次是要new一个完全不同的object的(这是react的特性决定的),你的state越大,开销就越大。其二是只要这个state有变化,connect它的组件就会重新渲染。当然redux并不傻,它做了很多优化,比如通常用redux的人会使用像immutablejs或者类似的第三方库来实现其new state,可是,这是人干的事么?有问题不从根源上解决,却引入本身就有争议的第三方库(之所以有争议,是因为如果你使用的方法错误,性能会变得更差,这也没什么,问题是它非常容易被错误的使用)。
而如果你用context的话,一个页面推荐维护多个context的,只有那些相关度非常高的state才放到一个context里面。这样每个context非常小,new state的开销也小,其需要重新渲染的组件也少。
3. 代码维护困难。
第一是connect其实是HOC,也就是组件二次封装。显然封装会影响可读性。这也不是关键,关键是redux团队犯了一个很扯淡的错误,就是connect的HOC传给被封装组件的props是开发者自己定义的,而不是redux定义的。这有什么区别呢?比如说你在读别人的代码的时候,如果看到props.redux.xxx,你会意识到xxx是connect HOC过来的。但是如果你碰到的是 props.xxx,你首先会想xxx是上层commponent传过来的,然而检查后你发现竟然不是。简单说这个问题给props的来源带来不确定性。
第二是redux本质是面向过程编程,而不是hooks所带来的面向属性编程。什么意思呢?所谓过程就是初始化state,connect,reducer。你读别人的代码,你能从connect处很容易的知道它对应的reducer么?你能很容易找到state的初始值么?不能。为什么?因为这三个过程是分开的,每个过程里面都包含很多property,你需要去找property。如果用context的话,可以轻松的实现按照属性封装,就是每个属性一个context,它的初始值,reducer(如果是单一属性就可能不需要reducer)以及可能需要一些其他逻辑都可以写在一起。同时和connect对应的是useContext,useContext比connect要简洁,并且通过useContext传过去的实例,你可以很容易的找到对应的context的属性的几乎所有内容。
有些人会把redux的性能自动优化啊,调试工具啊说成redux的优点,其实这恰恰是它的不足,react官方的调试工具都满足不了你的逻辑,你的逻辑是得有多差?
2. Hooks替代HOC和render props。
首先是hooks(这里指custom hooks,下同),HOC,render props到底是做什么的?
对于新手,并不是很容易理解它们是做什么的,至少我没有很容易理解。
我现在理解为它们是为了共享一些维护state的逻辑。
什么意思呢?首先它共享的是逻辑,但是共享逻辑你随便封装一个函数不就可以了么?问题就出在它是共享维护state的逻辑。普通的逻辑的和维护state的逻辑的区别就是,维护一个state的逻辑,当state发生变化时,用到这个state的组件是要重新渲染的,显然用普通的函数做不了这个事情。
那么有哪些维护state的逻辑呢?我大体上把它们分为两类:
第一类,一个状态组件维护state的逻辑,第二类,一个非组件维护state的逻辑。
其实一个状态组件本身就是在维护一个state,当用户操作它所渲染的UI的时候,state发生响应的变化。组件的逻辑负责维护state的变化。
那么非组件指什么呢?
比如说我维护一个user的登录状态,这个登录状态需要发送请求给后台去拿。那么这个登录状态就会有3个值{fetching,online,offline},而且可能会从fetching到online,或者从fetching到offline,就是会变化的。
比如说我需要记录用户鼠标的位置(x,y坐标),这个是随用户移动鼠标而变化的。
比如说我做一个定时器,每隔一段时间切换一下我维护的一个state。
等等,我相信还有其他很多。
之所以分为这两类,是因为只有render props可以共享状态组件,而hooks和HOC是不能的,这是因为如果你是组件,那么你的返回值(或者render函数)就必须是一个dom结构,那么就没有办法把维护的state,或者根据state的计算结果传递出去(hooks),当然你也就不能返回另一个组件(HOC)
所谓的render props共享状态组件,其实就是组件重用。这里有点理解上的倾向问题,当我们说render props的时候,我们往往想到的是重用父组件,而我们谈论组件重用的时候的,往往更倾向重用子组件。其实都是一个东西。render props在概念上是最说不清的,它在写法上有两种,其实是一回事,用哪种看你自己的倾向。我这里只说最顶层的调用的地方,就是同时要写父组件和子组件的地方,在这里要把子组件作为父组件的一个props传递过去,第一种是传递一个函数,返回值是一个组件的实例:<Parent render={()=><Child />}/> 第二种是传递组件本身 const <Parent render={Child}/>d当然第一种使用的比较多,原因是props变量一般用小写开头,而实例化组件时组件名一般用大写开头,这样用第二种就多了需要一个转换语句。
然后我们建立一个简单的模型分析下三者的结构。
我们假设有三个组件A,B,C需要共享一个维护state的逻辑S。那么:
hooks就是在A,B,C中直接调用S。上层使用的时候是 <A />
HOC就是把S封装成一个函数FunctionS,这个函数接收A,B,C之一作为参数,返回一个新的组件。比如接收的是A,那么返回的组件包含A和S的所有内容。上层使用的是 const WrappedA = FunctionS(A);<WrappedA />
Render props就是把S封装成一个组件,这个组件把A,B,C之一作为他的子组件。到底是A,B,还是C呢?这个在调用S的地方指定。上层使用的是 <S render={A} />
Hooks能替代HOC,render props么?
显然render props的共享状态组件的功能是无法被替代的,因为hooks实现不了。共享非组件的功能,基本都可以用hooks来替代,而且最好用hooks来实现,因为我们看到了,hooks的逻辑最直接,最容易理解。当然第一原则应该是根据业务实事求是,就是概念上该是什么就是什么,如果概念上就是HOC,那么就用HOC。实事求是是为了代码的更好维护。
原文地址:https://www.cnblogs.com/yy17yy/p/11485481.html