2

前言

在我们日常开发vue或react项目时,多多少少会遇到这样的需求:
我想在某个页面加载一个外部的资源文件,例如百度地图的js文件,还要在确保文件加载完成之后,执行一些逻辑。这些第三方文件,往往只在一个场景用到,并不想放在全局,这时候就需要实现一个文件的动态加载器。

代码

话不多说,先上代码:

// 如果没有传入type,那么根据url的后缀来确定type
const getTypeFromUrl = url => {
    const _type = /\.[^.]+$/.exec(url)[0];
    if (_type === '.js') {
        return 'text/javascript';
    }
    if (_type === '.css') {
        return 'text/css';
    }
}

// 格式化传入的文件列表
const formatFileList = file => {
    let list;
    if (typeof file === 'string') {
        list = [file];
    } else if (Array.isArray(file)) {
        list = file;
    } else {
        throw {error: 'The parameter must be a string or an array'};
    }
    let formatList;
    formatList = list.map((i) => {
        let url;
        let type;
        if (typeof i === 'string') {
            url = i;
            type = getTypeFromUrl(url);
        } else if (Array.isArray(i)) {
            [url] = i;
            type = (i[1] && i[1].type) ? i[1].type : getTypeFromUrl(url);
        } else {
            throw {error: 'The parameter must be a string or an array'};
        }
        if (!type) {
            throw {error: 'The URL type is not known'};
        }
        return {url, type};
    });
    return formatList;
}

// 根据类型创建元素
const createElement = ({type, url}) => {
    switch (type) {
        case 'text/javascript': {
            const element = document.createElement('script');
            element.setAttribute('type', 'text/javascript');
            element.src = url;
            return element;
        }
        case 'text/css': {
            const element = document.createElement('link');
            element.href = url;
            element.setAttribute('rel', 'stylesheet');
            element.setAttribute('media', 'all');
            element.setAttribute('type', 'text/css');
            return element;
        }
        default:
            break;
    }
}

// 加载文件
const loadElement = element => new Promise((resolve) => {
    const head = document.getElementsByTagName('head')[0];
    head.appendChild(element);
    if (element.readyState) {
        element.onreadystatechange = () => {
            if (element.readyState === 'loaded' || element.readyState === 'complete') {
                element.onreadystatechange = null;
                resolve();
            }
        };
    } else {
        element.onload = resolve;
    }
});

const dynamicFile = async file => {
    try {
        const list = formatFileList(file);
        const elementList = list.map(createElement);
        await Promise.all(elementList.map((i) => {
            return loadElement(i);
        }));
    } catch (error) {
        console.log(error);
    }
}

// 单例防止重复加载
const createSingle = fn => {
    const instanceMap = {};
    return function () {
        return instanceMap[arguments[1]] || (instanceMap[arguments[1]] = fn.apply(this, arguments));
    }
}

export default createSingle(dynamicFile);

如何使用

方法传入一个数组和一个string类型的name,name相当于一个标识,如果name相同,本页面加载过后,如果别的页面也有相同的name,则不会重复加载资源文件。

这里以加载百度地图的js为例,像这种url后缀不明确,无法得知是js还是css,所以需要手动指定type,如果url后缀是.js或.css,则可以省略这个参数。

dynamicImport([
    ['http://api.map.baidu.com/api?v=2.0&ak=VcIKGv3f9eq712B7idam46dd6in2fg8R&callback=baiduMap', {type: 'text/javascript'}],
    ['http://api.map.baidu.com/getscript?v=2.0&ak=VcIKGv3f9eq712B7idam46dd6in2fg8R&services=&t=20210225162129', {type: 'text/javascript'}]
], 'baiduMap').then(() => {
    let map = new BMap.Map('myMap');
    let point = new BMap.Point(116.397128, 39.916527);
    map.centerAndZoom(point, 6);
    map.enableScrollWheelZoom();
})

如果只有一个文件,且后缀为.js或.css,可以简写为:

dynamicImport('https://cdn.bootcdn.net/ajax/libs/Chart.js/3.7.0/chart.min.js', 'chart').then(() => {
    const ctx = document.getElementById('myChart').getContext('2d');
    ...
})

结尾

我是周小羊,一个前端萌新,写文章是为了记录自己日常工作遇到的问题和学习的内容,提升自己,如果您觉得本文对你有用的话,麻烦点个赞鼓励一下哟~

小绵羊
70 声望517 粉丝