into-piece / Step-By-Step

每天一题向前端架构师前进
4 stars 1 forks source link

组件封装思路 #40

Open into-piece opened 3 years ago

into-piece commented 3 years ago

hooks中传入传出api的设计 比如我们rn app需要引入录音功能,确认需要引入一个react-native-sound库, 根据这个库的pai 我们自己设计一个useSound的hook,因为他本身没有提供对应的hooks 需要有什么功能,点击play方法,pause方法,录音的duration属性,播放时长做一个进度条, 我们需要考虑传入传出,传入先传一个考虑录音的url, 尽量少的回调函数的传入,比如我想在报错的时候进行提示,这种我们是不会在hooks里面直接提示的,但按以前我开发的思路,可能会传一个处理error的回调函数进去,会想到再useCallback一下,嗯,觉得很完美,但是其实hooks只是来帮你管理状态的,越多的回调的传入反而让hook显示臃肿,更优化的方法是先用一个useState新增一个error,来捕获存储代码过程中的报错,最后将之抛出。

  1. error callback 回调作为参数传入
  2. 在对应调用方法如play方法中传入错误回调作为参数执行,将error传入
  3. play方法中return一个promise

这里获取error的设计,我是在自定义的play方法中接受第二个参数,进行return err,这样子

into-piece commented 3 years ago
import {
    useCallback,
    useEffect,
    useRef,
    useState,
    MutableRefObject,
} from 'react';
import Sound from 'react-native-sound';
import { downLoadFile } from '@/utils/utils';

type Status = 'pause' | 'play' | 'pending';

export const statusMap: Record<'PAUSE' | 'PLAY' | 'PENDING', Status> = {
    PAUSE: 'pause',
    PLAY: 'play',
    PENDING: 'pending',
};

export type SoundMethods = {
    play: (callback: (error: Error) => void) => void;
    pause: () => void;
};

export type Info = {
    status: Status;
    currentTime: number;
    duration: number;
};

const useSound = (
    url: string
): [SoundMethods, Info, MutableRefObject<Sound | null>] => {
    const soundRef = useRef<Sound | null>(null);
    const timer = useRef<any>(null);
    const [status, setStatus] = useState(statusMap.PAUSE);
    const [currentTime, setCurrentTime] = useState(0);
    const [error, setError] = useState('');
    const [duration, setDuration] = useState(0);

    const play = useCallback(
        (callback) => {
            setStatus(statusMap.PLAY);
            soundRef.current?.play((success) => {
                if (success) {
                    clearInterval(timer.current);
                    setStatus(statusMap.PAUSE);
                    setCurrentTime(duration);
                }
            });
            callback(error);
        },
        [error, duration]
    );

    const pause = useCallback(() => {
        soundRef.current?.pause();
        setStatus(statusMap.PAUSE);
    }, []);

    useEffect(() => {
        if (status === statusMap.PLAY) {
            timer.current = setInterval(() => {
                soundRef.current?.getCurrentTime((second) => {
                    setCurrentTime(second);
                });
            }, 1000);
        }
        return () => {
            clearInterval(timer.current);
        };
    }, [status, soundRef]);

    useEffect(() => {
        if (!url) {
            return;
        }
        setStatus(statusMap.PENDING);
        const { savePath, rnfs } = downLoadFile(url);
        rnfs.promise
            .then((res) => {
                if (res.statusCode === 200) {
                    soundRef.current = new Sound(savePath, '', (err) => {
                        if (err) {
                            setError(err);
                            return;
                        }
                        setDuration(soundRef.current?.getDuration()!);
                    });
                    setStatus(statusMap.PAUSE);
                }
            })
            .catch((err) => {
                setError(err);
            });
        return () => {
            soundRef.current?.release();
        };
    }, [url]);

    return [
        { play, pause },
        {
            status,
            currentTime,
            duration,
        },
        soundRef,
    ];
};

export default useSound;