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? - 转载自知乎

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容