前段时间 react hooks 特性刷得沸沸扬扬的,看起来挺有意思的,估计不少其他框架也会逐步跟进,所以也来尝试一下能不能用在小程序上。
react hooks 允许你在函数式组件中使用 state,用一段官方的简单例子概括如下:
import { useState } from 'react'; function Example() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }函数式组件本身非常简洁,不维护生命周期和状态,是一个可以让性能得以优化的使用方式。但是在之前这种方式只能用于纯展示组件或者高阶组件等,它很难实现一些交互行为。但是在 hooks 出现之后,你就可以为所欲为了。
这里有一份官方的文档,不明围观群众有兴趣的可以点进去了解一下: reactjs.org/docs/hooks-… 。
hooks 的使用目前有两个限制:
只能在函数式组件内或其他自定义 hooks 内使用,不允许在循环、条件或普通 js 函数中调用 hooks。
只能在顶层调用 hooks 。
这个限制和 hooks 的实现方式有关,下面小程序 hooks 也会有同样限制,原因应该也是类似的。为了能让开发者更好的使用 hooks,react 官方也提供了一套 eslint 插件来协助我们开发: reactjs.org/docs/hooks-… 。
下面就来介绍下在小程序中的尝试~
函数式组件小程序没有提供函数式组件,这倒是很好理解,小程序的架构是双线程运行模式,逻辑层执行 js 代码,视图层负责渲染。那么声明在逻辑层的自定义组件要渲染在视图层必须保证来两个线程都存在自定义组件实例并一一对应,这样的架构已经成熟,目前对函数式组件并没有强烈的需求。在基础库不大改的情况下,就算提供了函数式组件也只是提供了另一种新写法而已,本质上的实现没有区别也不能提升什么性能。
不过也不排除以后小程序会提供一种只负责渲染不维护生命周期不做任何逻辑的特殊组件来优化渲染性能,这种的话本质上就和函数式组件类似了,不过函数式组件较为极端的是在理论上是有办法做到无实例的,这个在小程序中怕是有点困难。
言归正传,小程序没有提供函数式组件,那么就强行封装出一个写法好了,假设我们有一个自定义组件,它的 js 和 wxml 内容分别是这样的:
// component.js const {useState, useEffect, FunctionalComponent} = require('miniprogram-hooks') FunctionalComponent(function() { const [count, setCount] = useState(1) useEffect(() => { console.log('count update: ', count) }, [count]) const [title, setTitle] = useState('click') return { count, title, setCount, setTitle, } })一个很奇葩的例子,但是能看明白就行。小程序里视图和逻辑分离,不像 react 可以将视图和逻辑写到一起,那么小程序里的函数式组件里想返回一串渲染逻辑就不太科学了,这里就改成返回要用于渲染的 state 和方法。
PS:wxml 里不支持 bindtap="setCount(count + 1)" 这种写法,所以参数就走 dataset 的方式传入了。
FunctionComponent 函数其实就相当于封装了小程序原有的 Component 构造器,它的实现类似这样:
function FunctionalComponent(func) { func = typeof func === 'function' ? func : function () {} // 定义自定义组件 return Component({ attached() { this._$state = {} this._$effect = {} this._$func = () => { currentCompInst = this // 记录当前的自定义组件实例 callIndex = 0 // 初始化调用序号 const newDef = func.call(null) || {} currentCompInst = null const {data, methods} = splitDef(newDef) // 拆分 state 和方法 // 设置 methods Object.keys(methods).forEach(key => { this[key] = methods[key] }) // 设置 data this.setData(data) } this._$func() }, detached() { this._$state = null this._$effect = null this._$func = null } }) } 复制代码实现很简单,就是在 attached 的时候跑一下传入的函数,拿到 state 和方法后设置到自定义组件实例上就行。其中 currentCompInst 和 callIndex 在 useState 和 useEffect 的实现上会用到,下面来介绍。
useState 和 useEffect这里的一个难点是,useState 是没有指定变量名的。初次渲染还好,二次渲染的话要找回这个变量就要费一段代码了。
PS:后续的实现除了参考了 react 的 hooks 外,也参考了 vue-hooks 的尝试,有兴趣的同学也可以去观摩一下。