June 12, 2019

项目小记

react hooks 的文章已经漫天飞舞了,我也不打算去履行人类的本质。关于前端,我觉得挺茫然又无动力,技术更替太快,激动之心很容易因为可以激动的事情太多太广而索然无味。hooks的真香很快就没有了,往后展望,看看react17的一些消息,感觉此时也是只小白鼠罢了。

对react的研究很少,项目的前辈丢锅,我接着,然后继续下去。这就是接触的缘由。语法,生命周期这些主要的东西一览,就要提起裤子干活了。hooks,给撸代码多了很多想象力。分离了状态业务后,可以复用,也可以满足某些洁癖。

自己撸hooks是挺有趣的事情,虽然没有把函数当参数传过去,不过返回函数那种高阶手感很好,有一等公民的味道。

Hooks

在最近的项目里,我对store,请求都做了包装。正所谓磨刀不误砍柴工,store,还有请求都能自动存到本地缓存,请求的可以配置缓存时间,可以广播结果,可以复用请求结果。然后进一步包装,封装成为钩子,用起来感觉格外轻便。

 const [submit, loading, , err] = useQuery("login");
 
 
 function login() {
     if (usr && pwd && !useErr && !pwdErr) submit({
         username: usr,
         password: pwd
     });
 }

useQuery 返回请求方法,状态,结果,错误
数组形式的返回很像其他语言比如golang的设计_,err := balbalba...

过去的写法,需要用回调的形式,改变UI状态,赋值结果都要手动去撸,有点麻烦。想想所有请求你需要的也都是这些东西,执行,状态,结果。所以按这个需求去设计自己的钩子就好。

Api

api的封装和配置也是值得去分离的。比如我的请求方法只需要配置文件里加上:

// 登录
    login: ['users/login', 1, {
        before({username, password}) {
            const k1 = '...';
            const k2 = '...';
            return {
                username: username,
                password: encrypt(password, k1, k2)
            }
        },
        done(data) {
            if (data.token) {
                const t = data.token;
                delete data.token;
                Object.assign(globalStore, data);
                setTimeout(() => {
                    globalStore.token = t;
                }, 100);
            }
        },
        fail() {
        }
    }],

一些数据很少会改变的请求可以放本地然后缓存:

 getLottery: ['games/lottery', 0, {
        cacheTime: 36e5,
        storage: localStorage,
        after({isSuccess, data}) {
            if (isSuccess) {
                const o = {};
                Object.keys(data).forEach(k => {
                    const item = data[k];
                    item.forEach(v => {
                        o[v.id] = {...v, cate: k}
                    })
                });
                return {
                    isSuccess,
                    data: o
                }
            }
        },
        done(data) {
            betStore.lotteries = data
        }
}]

...想想可圈点的的东西没有多少,基本上都是设计上的体验。记住不要多次做同样的事情,思考如何划分眼前的事情,那些是固定的那些是需要配置化的,然后设计思路自然就有了苗头。

Store

这个是我的store设计 缺陷是对象内部内容改变不会触发存储,需要手动去赋值触发。
比如 store.a={c:{d:1}} d变为2时候 sessionStorage 是不会变化的 需要store.a=store.a;
好处是简单粗暴了很多麻烦事情,比如清理到初始值,比如添加一个store。

import React from 'react';
import {observable, extendObservable} from "mobx"
import {Observer} from "mobx-react-lite"
import {encrypt, decrypt, encStoreKey} from '../utils';

const stores = {};
const loadStore = o =>
    Object.keys(o).forEach(
        k => {
            const s = o[k];
            const keys = [];
            const ini = JSON.parse(JSON.stringify(s));
            const ext = [];
            Object.keys(s).forEach(a => {
                const v = s[a];
                if (!/^_/.test(a) && 'function' !== typeof v) {
                    const key = '__' + a;
                    s[key] = v;
                    delete s[a];
                    const _k = encStoreKey(k + '.' + a);
                    keys.push([_k, a]);
                    ext.push({
                        get [a]() {
                            return this[key];
                        },
                        set [a](va) {
                            this[key] = va;
                            sessionStorage.setItem(_k, encrypt(JSON.stringify(va)))
                        }
                    });
                }
            });
            s.sync = function () {
                keys.forEach(([_k, k]) => {
                    const v = sessionStorage.getItem(_k);
                    if (v !== null) this[k] = JSON.parse(decrypt(v))
                })
            };
            s.clear = function () {
                Object.assign(this, ini)
            };
            const st = stores[k] = observable.object(s);
            ext.forEach(a => extendObservable(st, a));
            st.sync();
        }
    );

loadStore(require('./global'));
loadStore(require('./menu'));
loadStore(require('./bet'));
loadStore(require('./trend'));

export {stores}

export function useStore(...store) {
    const o = {};
    store.forEach(k => o[k] = stores[k]);
    return fn => props=> <Observer>
            {() => fn(o, props)}
    </Observer>
}

附带的useStore 算是简单包装下,撸页面大概简化了一点点?比如:

export const BetHead = useStore('betStore')(
    ({betStore: {current_number}},
     {endTime = 0, lastIssue: {issue = '---', lottery = []} = {}}) => {
        return <div className={"bet-hd"}>
            <div className={"lg"}/>
            <div className={"timer"}>
                <div className={'i'}>
                    <span>第{current_number}期</span>
                    <div>投注截止</div>
                </div>
                <Timer time={endTime}/>
            </div>
            <div className={"no"}>{issue}</div>
            <div className={"ball"}>
                {lottery}
            </div>
            <div className={"tool"}/>
        </div>
    });

写自己的store就很简单了,就一个普通对象:

export const globalStore = {
    token: '',
    username: '',
    showThemeSelect: false,
    allowed_tranfer: 0,
    android_url: false,
    announcement: [],
    balance: '',
    banners: [],
    bonus_percentage: null,
    daysalary_percentage: null,
    has_fund_password: 1,
    ios_url: false,
    max_prize: '',
    max_withdraw: '',
    prize_group: '0',
    today_withdraw: 0,
    version: '',
    withdraw_default_max_amount: '',
    withdraw_default_min_amount: ''
};

采坑

写钩子不小心可能就会写出一个不停刷新的怪物出来,务必在每个钩子都写个输出,至少控制台能看个明明白白。

我对请求做了Rxjs里AsyncSubject的单例包装,为了多播复用,之前刻意在useEffect 返回时候去unsubscribe,然后那个Subject还在用,然后就报错了。什么时候去正确的退订我到现在还有点囧,rxjs我也就看了那么几个api,毛皮都算不上。大概是一脸囧逼撸出了自己要的东西。

自己的时间真的太局限了,想要去学,也不知该割工作时间还是割生活时间。