props
对象并返回一个 React 元素
,它本质上就是基础的 JavaScript函数。
Class组件的写法创建方法有两种,下图是兼容es5写法的React.createClass定义的组件,这也是react一开始的创建组件的方法。
propTypes
用来处理校验定义属性的类型校验,PropTypes是从React中获取的,在React16.0.0版本以后废弃了这种写法,PropTypes被单独提取到prop-types
包中,所以先比较新的版本中,我们要进行类型校验,需要手动引入 prop-types 这个模块getInitialState
用来初始化state,getDefaultProps
用来定义props的默认属性值,这两个函数的用法有点类似于现在vue中的data和props方法render函数
,它会返回 react node用来渲染视图这是一个现在常见的ES6的class react组件写法
Component API
, 配合一些代码转换工具我们可以使用最新的es语法render函数
用来渲染视图,由于es6支持了方法简写,render以及其他定义的函数已经不需要声明function 关键字super
调用,ES6 规定,子类的构造函数必须执行一次super方法,如果子类没有定义constructor方法,这个构造函数会被默认添加并调用super。 如果我们声明了constructor函数,就必须在构造函数里调用super,只有在正常调用super(props)方法后才可以访问this对象,在constructor中如果要访问this.props需要在super中传入props,当然也可以直接通过props访问。但是无论有没有定义constructor,super是否传入props参数,在react的其他生命周期中this.props都是可以使用的,这是React自动附带的。字段名 = 引用值
。
在 constructor 方法被执行前, 实例上已经被赋值了该字段内容。
所以现在比较流行的写法是这样的
根据公有类字段语法 我们可以直接通过 字段名 = state
对象内容来声明state对象。
由于箭头函数没有自己this,在这里它会指向当前类的实例。借用公有类字段的提案,我们可以直接声明一个 箭头函数来修正方法的this指向。
这样我们就无需在构造函数里去创建state。也不需要在构造函数里写一大堆function绑定this。如果没有其他的操作,我们就完全可以省略构造函数。
PureComponent
内部是继承React.Component来的,在React.Component组件中我们可以使用shouldComponentUpdate来判断是否重发重新渲染,而 React.PureComponent 内部使用 shallowEqual 进行浅比较。
浅比较会比较更新前后的state和props的长度是否一致,判断是否新增或者减少了props或state的数据个数。
然后它还会调用内部的objectis
方法浅层对比前后的state和prop,objectis类似于es6的 Object.is方法, Object.is 类似于 === 全等运算符,只是在比较 +0 跟 -0 时表现的不太一样, === 返回的是true 而他是false。
上图是一个很简单的函数组件例子,虽然说函数组件本质上就是 一个 JavaScript 函数, 在例子里看起来没有任何使用react的api或者方法,但是我们仍然引入了react,这是因为在组件内部返回的react 元素 使用了 jsx,他实质上是React.createElement
的语法糖,配置好babel就可以为我们编译jsx,简化了我们写createElement的过程。
函数组件不需要声明类,可以避免大量的譬如extends或者constructor这样的代码 也不需要处理 this 指向的问题。 更加纯粹的是一个函数就是一个组件,React 官方说的 React 组件一直更像是函数,我们写函数式组件似乎也是更加贴近react的原则。 引用透明性是函数式编程的一个概念,我个人觉得函数组件遵循了纯函数的概念。
引用透明性是函数式编程里的概念。
这是一个 redux 里的 reducer 函数。在 redux 里 reducer
需要被定义为一个纯函数,它符合纯函数的几个定义:
很多时候一开始我们写的代码或者组件都是比较简单的,我们可能会选 函数组件来完成一个 简单的功能模块。但是越到后面可能功能就变的愈加的复杂了,函数组件内可能需要一些自己的状态或者生命周期了。 这时候想要实现这些功能可能就需要把它借助 高阶组件 或者 render props 帮它包装一层class 的父组件,这样它就间接的拥有了状态跟生命周期。但是这也都只是在函数组件外借助了 class 组件的能力。
由于函数组件在每次渲染时候,组件内部都会从上到下重新执行一遍,它也没有办法有自己内部的状态,也无法产生副作用,为了增强函数组件,hook
就出现了。
Vue 在放出可能到来 3.0 更新的内容,上图就是这次更新变动比较大的 funtion based api
.
对比右侧2.0的写法, template 就是 之前的模板语法。比较特殊的就是 setup 函数。 这里的value是一个包装对象,它就是组件内部的状态,这个看起来跟react 的 usestate hook 还是非常像的。 onMounted 对应的就是以前的生命周期函数 mounted,方法最后的返回对象有点像之前的data方法里的内容被绑定到了template模板语法上,setup 看起来就是一个简单的函数。
vue作者提到了function based api
借鉴了 react hook
的思想。
由于typescript对函数的类型推导能力比较好,之前vue组件比较流行的那种对象的写法现在看起来也变成了setup这样一个函数了。
至于更灵活的逻辑复用能力,这个在后面会看到是如何进行逻辑复用的。
React 16.8
的新增特性。它是在函数组件的基础上引入了一些新API,可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
我们看到这里使用了一个 useState
的API,它可以在调用时传入参数,并且返回一个数组。数组中有两项内容,第一个是状态(state)、第二个是设置状态(setState),使用es6 数组解构的特性,我们可以根据我们的需要去语义化命名,useState中的init参数只会在方法在第一次初始化以后,即使函数组件被更新,它的state也不会被重置,所以它可以一直保留着当前的状态。
这也就让函数组件拥有了内部状态,以及生命周期。
在react 的 class 组件中,我们会定义一个数组类型的 state,在对修改了引用中data里的一些值后,调用this.setState 方法会触发重新渲染,但是在同样的操作在hook里并不会触发更新。
调用我们设置 hook 的 setData 更新函数,数并传入当修改过引用类型的data 去触发更新时,React将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较前后 state)由于引用地址没变化 所以不会发生重新渲染。 我们可以使用图中下面的两种方式浅拷贝对象 然后再进行操作,即可解决这一问题。useEffect的规则如下:
React内部会创建对应的 hook 节点,react 第一次初始化组件时内部会将这些节点结构通过 next 依次连接起来,形成一个单向链表结构,每一次调用setter 就 dispach 一个对应的action方法,将action存储在queue中。
在后续触发渲染时,react 会调用 queue 队列中的内容,queue 也是一个链表结构,它是收集我们调用setter方法,queue 会依次执行内部的action,从而获取到最新的状态值,状态值会被更新到memorizedState上,然后将状态反应到对应的hook字段中形成绑定。所以当我们打乱或者增加减少hooks时,会造成hook内部顺序错误,从而导致无法正常工作。
上图是组合成的一个hooks单向链表结构,内部依次通过next指向下一个hook。
尝试通过修改hook数量,在第一次初始化时候我们调用了三个usestate hook,react也会在它内部依次收集了三个hook。 第一次渲染时,页面可以正常的渲染出内容,当我们点击了一个 setter 方法,重新触发了渲染时,会由于 hook 数量比之前的少而导致页面错误,无法正常渲染。所以保证每次都是相同的调用顺序才能正确的使用hook。
Hooks API是专门为函数组件设计的,所以我们只能在函数式组件内部或者自定义的hook中使用hooks,包括usestate、useeffect、userref 等以及自定义的hook api。而且自定义hook,也是无法在class组件内部使用的。 当然正常的函数组件,即使在内部使用了hook,我们可以也class组件内部复用该函数组件,这个是不会影响的。componentDidMount: 当第二个参数为空数组时,回调只会被触发一次,类似于componentDidMount。
componentDidUpdate: 当 useEffect 不传入第二个参数时,在第一次初始化和每次重新渲染后都会触发回调。这与 componentDidUpdate 有些不同,componentDidUpdate 生命周期在初始化挂载的时候不会被调用,我们可以使用 useRef 钩子来存储一个值来判断函数是否为第一次调用useRef返回一个可变的ref对象,它不仅可以绑定dom的引用,也可以用来存储一些值。其.current
属性被初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。变更 .current
属性不会引发组件重新渲染。
componentWillUnmount 前面提到每个 useEffect 都可以返回一个清除函数。配合这一特性,我们可以任然将第二个参数设置为一个空数组,这样清除函数会在组件卸载前被调用,我们可以在这里面清除一些事件监听器等。更方便的是我们可以将 didMount 和 unMount 写在同一个 useEffect 钩子中。
componentWillReceiveProp: 我们想要模拟之前的 componentWillReceiveProps 生命函数,可以将第二个参数设为需要观察的props参数。为了比对前后的 props 变化,可以用 useRef 来存储旧的props来达到目的。setState的第二个参数 我们知道在使用setState方法时,可以传入第二个参数,这个参数是个回调函数,它在设置完 state 以后 会被调用。 我们在hook里也可以通过用useEffect 钩子传入要监听的 state,当该state更新以后,回调函数会被调用。
React 规定 自定义hook 需要用 use 开头来定义,自定义hook虽然不是一个组件,它不需要返回 React Node 元素节点,如果返回了元素,它就又变成的一个函数组件,但是虽然这样,它同样的可以在方法内部使用 状态 usestate 和生命周期 useeffect 等其他的 hooks。他拥有自己的内部状态和生命周期。
比较特殊的是 自定义hook 返回的内容可以任何类型,你可以单纯的返回一个值,也可以返回一个对象或者数组甚至是方法,返回的内容取决于在调用你创建的自定义hook时所需要的一些属性。
我们在这里定义了一个名为 useOnline 的自定义hook, 我们用它来判断当前的网络连接状态。首先hook的方法名是以use开头的,后面的名字可以根据你的意愿来定义,大部分时候hook都会遵循驼峰命名法,只要是以use开头,后面的定义是没有限制的。不过我尝试了一下即使不使用use开头创建的自定义hook其实也是正常运行的。react 约定以use开头是为了通过一些自动检查工具来来校验 这些use开头的hook内部是否违反了 hook的使用规则。不过还是最好遵循使用use开头,驼峰命名这样一个约定。
然后调用 usestate 创建一个 自定义hook内部的状态, 传入了 navigator online 初始化当前的网络状态,并且在自定义 hook 内部拥有了自己的状态。
接着在使用 useeffect 钩子内,我们需要监听网络状态的变化,联网或者断网的监听方法只需要在组件创建时调用一次即可,所以我们在传递useeffect的第二个参数时,只传递了一个空的数组,这样它就只会在 didmount 时被调用一次。
同样的在销毁组件时,我们也需要清除掉这些监听事情。在 effect 钩子的返回函数中清理掉这两个监听方法即可
最后我们将 当前的状态值 作为自定义hook的返回值,这样就完成了一个监听网络状态的自定义hook。
我们在一个最简单的函数组件中引入这个hook。 然后在函数组件内我们调用这个自定义hook,hook会返回当前的网络状态,这是一个很简单的函数组件,我们只渲染当前的网络状态。
当我们渲染出组件时候他渲染出了当前的网络状态, 当我们切换为离线状态时,可以看组件被重新渲染出了当前的网络状态值。这样,当我们创建出一个自定义hook时 就可以在很多地方调用直接复用这段代码,达到了逻辑复用的目的。
不过大家可能觉得这不就是抽象一个方法么? 好像没有自定义hook 我们也可以把一些方法抽象到一些工具函数中, 写一些 helper 或者 utils来完成这些功能 。 但是其实是不一样的,自定义的hook强大之处在于拥有状态,当状态值改变时,它可以触发调用组件的重新渲染,而且自定义hook可以跟随着组件的生命周期,在不同的生命钩子阶段,我们可以处理一些事件。
我们可以选择自己去 实现 一个 useDidMount 自定义hook,实现起来也非常简单。只需要在钩子内部调用 useeffect hook,将它的第一个参数传递为我们自定义钩子的回调函数参数,将第二个可选的参数设为一个空数组,这样就只会在 didmount时候被调用一次。
这个开源项目帮我们收集了一系列已经封装好的自定义hook,比如这些生命周期钩子,我们只需要引入即可实现这些生命周期。包括一些 didMount, unmount, update 等钩子。
分享一个React Hook弧形进度条组件react-arc-progress,这个之前是一个es6方式写的插件,然后之前为了跟进 react hook,将它改造成了一个 react 组件。
重复的造轮子肯定是没有意义的,所以要添加一些独特的功能让它变得更不一样些:
GitHub地址,请给个star吧 ◔ ‸◔