React Hooks你真的会用吗?

首先看一个标准的使用reac hooks的案例:

const useUserList = () => {
    const [pending, setPending] = useState(false);
    const [users, setUsers] = useState([]);
    const load = async params => {
        setPending(true);
        setUsers([]);
        const users = await request('/users', params);
        setUsers(users);
        setPending(false);
    };
    const deleteUser = useCallback(
        user => setUsers(users => without(users, user)),
        []
    );
    const addUser = useCallback(
        user => setUsers(users => users.concat(user)),
        []
    );
    return [users, {pending, load, addUser, deleteUser}];
};

提供了用户列表,有加载、添加、删除三个功能,如果团队能用到这种粒度,也算前10%水平了吧。

但是,这个hook的实现其实是有问题的,这个hook包含了多方向的功能,让我们拆一拆:

1.加载一个远程数据,并且控制“加载中”状态。
2.往一个数组中增加或删除内容。
3.将一份数据(列表)和这份数据的相关操作(add、delete)合在一起返回。
4.指定加载用户列表这个具体业务场景。

一但这样去拆解,不难发现其实1-3全是通用能力,而不是业务相关的。
所以我得出来一个比较经典的hook的分层拆解的玩法。

状态与操作封装

如同面向对象强调的是状态(properties)与操作(methods)的封装,虽然我们在React里大量追求函数式,但也并不代表我们应该反对面向对象的封装特性。

把一个状态和它强相关的行为放在一起,显而易见地是一种合理的编程模式。

因此,在hook分层的最底层,我建议大家都有一个功能有,叫作“给我一个值和一堆方法,我帮你变成hook”,在我的实现里我叫它useMethods。这个东西超容易实现:

export const useMethods = (initialValue, methods) => {
   const [value, setValue] = useState(initialValue);
   const boundMethods = useMemo(
       () => Object.entries(methods).reduce(
           (methods, [name, fn]) => {
               const method = (...args) => {
                   setValue(value => fn(value, ...args));
               };
               methods[name] = method;
               return methods;
           },
           {}
       ),
       [methods]
   );
   return [value, boundMethods];
};

什么你说太绕了都快晕了?玩React哪有不绕的道理……

封装常用数据结构

有了与任何类型都无关的基础的方法封装,我们就可以在它的基础上衍生出最常见的数据结构了。正如原生的数组有push、pop、slice等方法,原生的字符串有trim、padStart、repeat等方法,把这些东西包一包也能变成“数组hook”、“字符串hook”这样的基础hook。这里需要注意的是,你不能把useArray的push直接引到数组的push上去,因为我们对状态的更新要求是immutable的,所以push要对应concat,pop要对应slice,总之这是很容易的:

const arrayMethods = {
    push(state, item) {
        return state.concat(item);
    },
    pop(state) {
        return state.slice(0, -1);
    },
    slice(state, start, end) {
        return state.slice(start, end);
    },
    empty() {
        return [];
    },
    set(state, newValue) {
        return newValue;
    },
    remove(state, item) {
        const index = state.indexOf(item);
        if (index < 0) {
            return state;
        }
        return [...state.slice(0, index), ...state.slice(index + 1)];
    }
};

const useArray = (initialValue = []) => {
    invariant(Array.isArray(initialValue), 'initialValue must be an array');
    return useMethods(initialValue, arrayMethods);
};

相应的,数字我们也可以玩一玩:

const numberMethods = {
    increment(value) {
        return value + 1;
    },
    decrement(value) {
        return value - 1;
    },
    set(current, newValue) {
        return newValue;
    }
};

const useNumber = (initialValue = 0) => {
    invariant(typeof initialValue === 'number', 'initialValue must be an number');
    return useMethods(initialValue, numberMethods);
};

随你高兴吧,有闲情的可以把什么链表、树、队列、栈、堆、冠军树、红黑树全给来一遍,你高兴就好。

通用过程封装

数据结构毕竟还只是最基础的东西,我们不能只有数据结构就去写代码,我们还需要利用数据结构串起来的过程。
比如在最前面的例子里,对“异步调用”这个事情就是一个很经典的过程。
因此,我们可以有这样的一个hook,它的作用是“给我一个异步函数,我帮你调用它并管理异步状态”,我叫它useTaskPending,功能也简单,直接用useNumber去管一管异步状态就好:

const useTaskPending = task => {
    const [pendingCount, {increment, decrement}] = useNumber(0);
    const taskWithPending = useCallback(
        async (...args) => {
            increment();
            const result = await task(...args);
            decrement();
            return result;
        },
        [task, increment, decrement]
    );
    return [taskWithPending, pendingCount > 0];
};

再给它进一步,我们想要不仅仅能调用过程,还能把结果给同步到状态里:

const useTaskPendingState = (task, storeResult) => {
    const [taskWithPending, pending] = useTaskPendingState(task);
    const callAndStore = useCallback(
        () => {
            const result = await taskWithPending();
            storeResult(result);
        },
        [taskWithPending, storeResult]
    );
    return [callAndStore, pending];
};

拼装成业务

有数据结构,有过程,现在再去拼一个业务就简单了,像这样:

const useUserList = () => {
    const [users, {push, remove, set}] = useArray([]);
    const [load, pending] = useTaskPendingState(listUsers, set);
    return [users, {pending, load, addUser: push, deleteUser: remove}];
};

你可以看到,很直观地是代码少了那么几行,进而每一行代码都有更强的语义化了,比如useArray明确这里就是一个数组,对比useState还要去看参数才知道是数组还是对象干净利落了不少。更重要的是,基于前面的方法、数据结构、过程这3层,你可以更快地搞出“文章列表”、“评论列表”、“用户详情”等等一系列的业务,而不需要重复地去管理pending、管理数组之类的冗余的事情。

兴致有限,就先简单地介绍一下hook最最基础的状态管理部分的实践玩法。顺便这代码能不能跑我不知道,只代表想法不代表实现~其它如context怎么玩、effect怎么玩、ref有多牛逼、memo有多坑、subscription怎么用,甚至怎么快速写一个小型redux等等,就不赘述了。

欢迎关注我的个人公众号【小恶魔君】,全是原创的有趣有料有理有据的内容。
如何去合理使用 React hook? - 转载自知乎

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,333评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,812评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,016评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,293评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,650评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,788评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,003评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,741评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,462评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,681评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,168评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,528评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,169评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,119评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,846评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,719评论 2 274

推荐阅读更多精彩内容