风不识途

风不识途 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

学习前端

个人动态

风不识途 收藏了问题 · 1月18日

redux结合immutable.js和redux-persist报错

clipboard.png

store.js

import { createStore, compose, applyMiddleware } from "redux";
import { routerMiddleware } from "connected-react-router/immutable";
import { createMigrate, persistStore, persistReducer } from "redux-persist";
import createEncryptor from "redux-persist-transform-encrypt";
import immutableTransform from "redux-persist-transform-immutable";
import storage from "redux-persist/es/storage";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";
import { createBrowserHistory } from "history";
import createRootReducer from "../reducers";
import rootSaga from "../sagas";
import config from "../../config/base.conf";
import { authTokenMiddleware } from "../middleware/authTokenMiddleware";

export const history = createBrowserHistory();
// create the router history middleware
const historyRouterMiddleware = routerMiddleware(history);
// create the saga middleware
const sagaMiddleware = createSagaMiddleware();

// 组合middleware
const middleWares = [sagaMiddleware, historyRouterMiddleware, logger, authTokenMiddleware];

const migrations = {
  0: state => {
    return {
      ...state,
      device: undefined
    };
  },
  2: state => {
    return {
      device: state.device
    };
  }
};

const encryptor = createEncryptor({
  secretKey: "hiynn",
  onError: function(error) {}
});

const persistConfig = {
  transforms: [encryptor, immutableTransform()],
  key: config.persist,
  storage,
  version: 2,
  migrate: createMigrate(migrations, { debug: false })
};

const finalReducer = persistReducer(persistConfig, createRootReducer(history));

export default function configureStore(preloadedState) {
  const store = createStore(finalReducer, preloadedState, compose(applyMiddleware(...middleWares)));
  let persistor = persistStore(store);
  sagaMiddleware.run(rootSaga);
  return { persistor, store };
}

reducer.js

// import { combineReducers } from "redux";
import { combineReducers } from "redux-immutable";
import { connectRouter, LOCATION_CHANGE } from "connected-react-router/immutable";
import layoutPageReducer from "./layoutPageReducer";
import authReducer from "./authReducer";

export default history =>
  combineReducers({
    router: connectRouter(history),
    layoutPageReducer,
    authReducer
  });

authReducer.js

import { handleActions } from "redux-actions";
import { authTypes } from "../actions/authAction";
import moment from "moment";
import { Map } from "immutable";

const initState = Map({
  user: null,
  token: ""
});

const authReducer = handleActions(
  {
    [authTypes.AUTH_SUCCESS]: (state, action) => {
      return state.set("user", action.data.user).set("token").action.data.token;
    },
    [authTypes.SIGN_OUT]: (state, action) => {
      return state.set("user", null).set("token", "");
    }
  },
  initState
);

export default authReducer;

风不识途 收藏了文章 · 1月18日

前端下载二进制流文件

平时在前端下载文件有两种方式,一种是后台提供一个 URL,然后用 window.open(URL) 下载,另一种就是后台直接返回文件的二进制内容,然后前端转化一下再下载。

由于第一种方式比较简单,在此不做探讨。本文主要讲解一下第二种方式怎么实现。

Blob、ajax(axios)

mdn 上是这样介绍 Blob 的:

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据

具体使用方法

axios({
  method: 'post',
  url: '/export',
})
.then(res => {
  // 假设 data 是返回来的二进制数据
  const data = res.data
  const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
  const link = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.setAttribute('download', 'excel.xlsx')
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
})

打开下载的文件,看看结果是否正确。

在这里插入图片描述

一堆乱码...

一定有哪里不对。

最后发现是参数 responseType 的问题,responseType 它表示服务器响应的数据类型,由于后台返回来的是二进制数据,所以我们要把它设为 arraybuffer
接下来再看看结果是否正确。

axios({
  method: 'post',
  url: '/export',
  responseType: 'arraybuffer',
})
.then(res => {
  // 假设 data 是返回来的二进制数据
  const data = res.data
  const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
  const link = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.setAttribute('download', 'excel.xlsx')
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
})

在这里插入图片描述

这次没有问题,文件能正常打开,内容也是正常的,不再是乱码。

根据后台接口内容决定是否下载文件

作者的项目有大量的页面都有下载文件的需求,而且这个需求还有点变态。

具体需求如下

  1. 如果下载文件的数据量条数符合要求,正常下载(每个页面限制下载数据量是不一样的,所以不能在前端写死)。
  2. 如果文件过大,后台返回 { code: 199999, msg: '文件过大,请重新设置查询项', data: null },然后前端再进行报错提示。

先来分析一下,首先根据上文,我们都知道下载文件的接口响应数据类型为 arraybuffer。返回的数据无论是二进制文件,还是 JSON 字符串,前端接收到的其实都是 arraybuffer。所以我们要对 arraybuffer 的内容作个判断,在接收到数据时将它转换为字符串,判断是否有 code: 199999。如果有,则报错提示,如果没有,则是正常文件,下载即可。具体实现如下:

axios.interceptors.response.use(response => {
    const res = response.data
    // 判断响应数据类型是否 ArrayBuffer,true 则是下载文件接口,false 则是正常接口
    if (res instanceof ArrayBuffer) {
        const utf8decoder = new TextDecoder()
        const u8arr = new Uint8Array(res)
        // 将二进制数据转为字符串
        const temp = utf8decoder.decode(u8arr)
        if (temp.includes('{code:199999')) {
            Message({
                // 字符串转为 JSON 对象
                message: JSON.parse(temp).msg,
                type: 'error',
                duration: 5000,
            })

            return Promise.reject()
        }
    }
    // 正常类型接口,省略代码...
    return res
}, (error) => {
    // 省略代码...
    return Promise.reject(error)
})

更多文章,敬请关注

查看原文

风不识途 赞了文章 · 1月18日

前端下载二进制流文件

平时在前端下载文件有两种方式,一种是后台提供一个 URL,然后用 window.open(URL) 下载,另一种就是后台直接返回文件的二进制内容,然后前端转化一下再下载。

由于第一种方式比较简单,在此不做探讨。本文主要讲解一下第二种方式怎么实现。

Blob、ajax(axios)

mdn 上是这样介绍 Blob 的:

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据

具体使用方法

axios({
  method: 'post',
  url: '/export',
})
.then(res => {
  // 假设 data 是返回来的二进制数据
  const data = res.data
  const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
  const link = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.setAttribute('download', 'excel.xlsx')
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
})

打开下载的文件,看看结果是否正确。

在这里插入图片描述

一堆乱码...

一定有哪里不对。

最后发现是参数 responseType 的问题,responseType 它表示服务器响应的数据类型,由于后台返回来的是二进制数据,所以我们要把它设为 arraybuffer
接下来再看看结果是否正确。

axios({
  method: 'post',
  url: '/export',
  responseType: 'arraybuffer',
})
.then(res => {
  // 假设 data 是返回来的二进制数据
  const data = res.data
  const url = window.URL.createObjectURL(new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}))
  const link = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.setAttribute('download', 'excel.xlsx')
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
})

在这里插入图片描述

这次没有问题,文件能正常打开,内容也是正常的,不再是乱码。

根据后台接口内容决定是否下载文件

作者的项目有大量的页面都有下载文件的需求,而且这个需求还有点变态。

具体需求如下

  1. 如果下载文件的数据量条数符合要求,正常下载(每个页面限制下载数据量是不一样的,所以不能在前端写死)。
  2. 如果文件过大,后台返回 { code: 199999, msg: '文件过大,请重新设置查询项', data: null },然后前端再进行报错提示。

先来分析一下,首先根据上文,我们都知道下载文件的接口响应数据类型为 arraybuffer。返回的数据无论是二进制文件,还是 JSON 字符串,前端接收到的其实都是 arraybuffer。所以我们要对 arraybuffer 的内容作个判断,在接收到数据时将它转换为字符串,判断是否有 code: 199999。如果有,则报错提示,如果没有,则是正常文件,下载即可。具体实现如下:

axios.interceptors.response.use(response => {
    const res = response.data
    // 判断响应数据类型是否 ArrayBuffer,true 则是下载文件接口,false 则是正常接口
    if (res instanceof ArrayBuffer) {
        const utf8decoder = new TextDecoder()
        const u8arr = new Uint8Array(res)
        // 将二进制数据转为字符串
        const temp = utf8decoder.decode(u8arr)
        if (temp.includes('{code:199999')) {
            Message({
                // 字符串转为 JSON 对象
                message: JSON.parse(temp).msg,
                type: 'error',
                duration: 5000,
            })

            return Promise.reject()
        }
    }
    // 正常类型接口,省略代码...
    return res
}, (error) => {
    // 省略代码...
    return Promise.reject(error)
})

更多文章,敬请关注

查看原文

赞 13 收藏 6 评论 3

风不识途 赞了回答 · 1月18日

解决redux结合immutable.js和redux-persist报错

解决了,参考

import { createStore, compose, applyMiddleware } from "redux";
import { routerMiddleware } from "connected-react-router/immutable";
import { createMigrate, persistStore, persistReducer } from "redux-persist";
import createEncryptor from "redux-persist-transform-encrypt";
import immutableTransform from "redux-persist-transform-immutable";
import storage from "redux-persist/es/storage";
import createSagaMiddleware from "redux-saga";
import { createLogger } from "redux-logger";
import { createBrowserHistory } from "history";
import createRootReducer from "../reducers";
import rootSaga from "../sagas";
import config from "../../config/base.conf";
import { authTokenMiddleware } from "../middleware/authTokenMiddleware";

export const history = createBrowserHistory();
// create the router history middleware
const historyRouterMiddleware = routerMiddleware(history);
// create the saga middleware
const sagaMiddleware = createSagaMiddleware();
// create the logo middleware
const loggerMiddleware = createLogger({
  predicate: () => process.env.NODE_ENV === "development"
});
// 组合middleware
const middleWares = [sagaMiddleware, historyRouterMiddleware, loggerMiddleware, authTokenMiddleware];

const migrations = {
  0: state => {
    return {
      ...state,
      device: undefined
    };
  },
  2: state => {
    return {
      device: state.device
    };
  }
};

const encryptor = createEncryptor({
  secretKey: "hiynn",
  onError: function(error) {}
});

const persistConfig = {
  transforms: [
    immutableTransform()
    // encryptor
  ],
  key: config.persist,
  storage,
  version: 2,
  migrate: createMigrate(migrations, { debug: false })
};

const finalReducer = persistReducer(persistConfig, createRootReducer(history));

export default function configureStore(preloadedState) {
  const store = createStore(finalReducer, preloadedState, compose(applyMiddleware(...middleWares)));
  let persistor = persistStore(store);
  sagaMiddleware.run(rootSaga);
  return { persistor, store };
}
import { combineReducers } from "redux";
import { connectRouter, LOCATION_CHANGE } from "connected-react-router/immutable";
import layoutPageReducer from "./layoutPageReducer";
import authReducer from "./authReducer";
import indexReducer from "./indexReducer";
import indexMapReducer from "./indexMapReducer";

export default history =>
  combineReducers({
    router: connectRouter(history),
    layoutPageReducer,
    authReducer,
    indexReducer,
    indexMapReducer
  });

关注 1 回答 1

风不识途 赞了文章 · 1月1日

从2020看2021前端发展趋势

image

前言

图片

又到了年底,想简单谈谈这一年前端的发展,以及21年可能会出现的一些趋势。毋庸置疑,2020年确实是不平凡的一年,对前端来说,私以为可以用“大前端持续深耕,泛前端兼容并包”这十四个字来形容。这里需要明确一下我对“大前端”以及“泛前端”这两个概念的理解:首先大前端可以分为广义的“大前端”和狭义的“大前端”,市面上常说的大前端主要分为这两类,所谓广义的“大前端”是指以前端技术解决所有本属于其他领域问题的前端技术,这里其实是包含了后边所说的泛前端的概念,也就是说只要是使用前端技术去解决的都可以定义为“大前端”;而狭义的“大前端”是仅指垂直到后端领域的前端技术,其代表是以node.js为主的扩展的去解决后端领域的技术,如出现了诸如express、koa、egg、nest等等配套的后端技术框架,甚至出现了node的微服务框架,我这里不做说明仅指狭义的“大前端”概念。对于“泛前端”,这个概念没有歧义,通常指的都是跨端技术,比如客户端、桌面端,甚至HUB等等,上图中所示的仅仅指一部分框架,并未收录完全,毕竟js/ts的社区实在太太太太活跃了,下面简单分说一下“大前端”和“泛前端”个人的一些见解,是以前端三大框架Vue、React、Angular为核心进行横向和纵向的框架探讨

泛前端技术框架探讨

VueReactAngular
web端Vue全家桶React全家桶Angular全家桶
桌面端Electron/NwElectron/NwElectron/Nw/Cordova/Ionic
原生移动端WeexReact NativeCordova/Ionic
小程序端uniapp/mpvue/mapxtaro/Rax

大前端技术框架探讨

VueReactAngular
web端Vue全家桶React全家桶Angular全家桶
SSRNuxtNextUniversal
服务端无特定无特定可配合Nest

今年阿里前端练习生计划将前端领域划分了七大方向,分别是工程化方向、前端中后台方向、Node.js方向、跨端技术方向、互动技术方向、可视化技术方向、前端智能化方向,个人认为Node.js是一个整体的前端基础,并不能单算做一个方向,因而我将其替换为音视频方向,私以为可将前端方向划分为以下几种,下面我将在这几个不同的方向维度进行个人的一些阐述和拙见:

  • 中后台方向:微前端
  • 可视化方向:antv
  • 智能化方向:imgcook
  • 互动方向:Eva
  • 音视频方向:wasm
  • 工程化方向:severless、全链路工程
  • 跨端方向:kbone、rax

中后台方向

  1. 对于中后台方向,这是传统web方向,对于前端来说,2020年对于微前端的落地应用已有很多实践。因此,私以为微前端会作为未来前端大型应用的一种趋势,对于不同团队的不同技术栈的汇总整合是一个比较好的方案,虽然不同于服务端的那种微服务可以微的很彻底,但是前端加持着ts等的特性也是可以实现微化的效果,从而优化工时,提升效率,避免重复劳作;
  2. 既然第一点提到了ts,那这里我想说一说关于ts的问题,在19年我对ts的态度尚存观望,但在今年,我认为ts可以作为一种必备技能来考察和实践,并不是因为大家都在用,而是因为随着时间的发展,现在前端要承载的能力越来越重,项目也越来越大,ts可以很好的约定,对于后期的维护以及修改都可以很好的限制,对于大型团队、大型项目来说,ts是不二之选;另外,如果有团队真的要去开发服务端,那我真的建议你去用ts,如果你用过nest.js,你会发现和写java真的没什么区别,而且服务端的很多理念也可以通过ts的各种特性来使用,比如泛型、抽象类等。当然,如果是小团队,而且需要快速开发,对于小而美的应用,个人认为还是js适合,毕竟js是一门灵活的语言,哈哈哈

可视化方向

  1. 可视化这里,今年其实没有做多少实践,但是个人认为,这七个大方向而言,最容易出成果,或者说最能出大佬的,可能还真是可视化领域,如果能在可视化领域做到了前5%,那么我相信各大厂肯定会花钱养着你,毕竟我拥有你不一定重要,但是对手不拥有你对我很重要,你的技术壁垒就呈现了出来,也就在这个内卷的江湖获得了一些领先和优势,最起码裁员的优先级也不会那么靠前,哈哈哈
  2. 第一点说的有点儿远,说说实在的,个人觉得可以将阿里的antv作为可视化领域的一个标杆,针对各种特定领域进行展开,比如gis等,当然,其实可视化领域研究的深的话还是挺冷门的,这其实也会有一些机会,webGL/webAR/webVR等等都可以作为切入点,配合着下面的音视频领域,这两个的相互结合,确实还是能做出些东西的

智能化方向

  1. 智能化领域,个人认为其实核心不在于前端,而在于人工智能,而人工智能领域就不仅仅前端那么些东西了,对于封装好的如TensorFlow.js,我们是直接拿来用的,但是对于领面的模型建立其实才是关键,如果有同学想在智能化领域有所发展,个人建议还是要深钻一下,最好可以深造一下,毕竟这个东西是真的需要学术研究+产业实践的
  2. 在产业实践的前端领域,阿里还是比较领先的,比如imgcook,其核心目的是将图片转成前端代码,利用的机器视觉去识别图片中的不同的位置信息等,将其转成前端的代码,也就是所谓的D2C,即:Design to Code,今年的D2大会上又提出了P2C,即:Product to Code,但并不是真的将人类语言直接转换成code,而是对D2C的一个业务扩展,对于D2C某些业务代码利用一些特定的schema进行约定,让机器学习过程中多一些参数约束,从而提升代码实现度。虽然转化的代码还很冗余,但是对于一些初级的项目或页面,确实可以交给机器去实现,个人认为人工智能对低端重复的工作确实会进行替代,也算是对前端内卷做了一定的贡献,摊手...

互动方向

  1. 互动方向,目前大头还是在游戏方向,但是我还是认为页游或前端实现游戏主要还是做一些开胃菜的功能,并不能真正的将用户带进沉浸式的体验,对于小游戏引擎,今年阿里好像出了一个eva的引擎,感兴趣的同学可以看看
  2. 对于新体验方面,AR/VR/MR等可能在明年5g深化的时候会有一些落地实践,但可能还是不温不火,互动方向配合设计的新理念可能会是一个新的突破点

音视频方向

  1. 这里想说一说WebAssembly,虽然都说wasm是会替代掉js,但目前看应该还不太现实,wasm目前主要还是配合音视频领域多一些,另外就是一些额外的优化措施,配合rust进行优化等,如果有志于音视频领域有所建树的同学,wasm确实可以储备起来
  2. webrtc是音视频领域一个绕不开的技术,另外就是播放器的实现,video.js、flv.js等,对于一些视频播放的协议也要了解,如rtmp/rtsp等,今年疫情的关系,带动着直播领域的火热,前端音视频方向说不定会是一个风口

工程化方向

  1. 工程化方向就不得不提serverless,这是今年前端最火热的一个话题了,私以为serverless不仅仅是前端领域的变化,它可能改变目前的开发模式,以后没有前后端之分(ps:目前确实是有合的趋势),只有云工程师和端工程师之分,那么对于目前的前端来说,我们就不能仅仅只关注前端领域的一些内容了,docker、k8s等属于传统后端或运维的部分,我们也需要掌握,对于一些后端的思维及名词也需要熟悉,如限流、削峰、服务降级等。当然serverless的前提其实是云原生,如果没有云化,那实现serverless的效果应该不如目前这样,当然个人认为这是一个必然的趋势,从今年的云栖大会以及运营商5g云化来看,云网融合、云边协同应该会是主流
  2. 前端工程化另外一个方向就是全链路的工程化,从脚手架、low code、插件市场等全链路的提供,简化前端开发门槛,这里可以参考淘系的飞冰,对low code以及ide的插件提供都有涉及,以及ui组件库的提供等等,形成一个全链路的前端生态。这里可以说说low code和no code,no code是一个无需任何编码的现成的工具,就是完全没有编程的入口;low code是一个需要部分编码的工具,为了是给一些非专业人士但又有编程需要的人员使用,强调开发出来给别人用,常见的比如给运营人员用的h5编辑器,如易企秀、maka、ih5等

跨端方向

  1. 跨端方向是一个老生常谈的问题,主流还是要write once,run anywhere,常见的无非就是利用各种框架将各个DSL进行来回转换,但其实这是一个伪命题,本质是不可能达到大一统的局面的,抽象就很难具象,这两者需要有一个平衡,需要对具体常见具体分析
  2. 私以为小程序领域是端方向下一个各家大佬追逐的市场,因为app领域的跑马圈地都已基本形成,现在再去开发出一个现象级的app几乎很难很难了,但是将app作为操作系统,以小程序去带动用户,形成用户闭环,还是有很大市场的,类小程序应用,如pwa、快应用等都是这种承载方式的不同展现,对跨端来说,这个不失为一种发展选择。对于小程序的框架也出现了kbone、rax等,可能也会有一个类似w3c这样的一个小程序标准,但是像微信这种巨无霸应用,是否真的会遵守就是另一回事了,总之,其实也不失为一个选择

总结

回望2020,展望2021,总结如下:

  1. 大前端持续深耕,泛前端兼容并包
  2. 中后台微服务化,可视化行业细化
  3. 智能侧算法深化,互娱侧形态变化
  4. 音视频结构优化,工程侧全面转化
  5. 跨端侧具体改化,前端更加内卷化

好了,2021到了,愿大家在这个内卷的时代,都有自己的一技之长,形成自己的核心竞争力,升职加薪,再创辉煌,共勉!!!

查看原文

赞 44 收藏 26 评论 6

风不识途 收藏了文章 · 2020-11-15

javaScript 这些常用的代码块你知道多少?

前言


本文主要写的是自己常用的一些javaScript 代码块。记录常用代码,方便查阅。如发现错误,欢迎留言改正。

阅读三连:点赞(👍)、关注(😍)、收藏(📝)。

正文


1. 将Url参数转换成对象,没有参数时返回空对象


@毛宗太 在【毛宗太】的指正下,新增兼容hash(#)模式;

function getQueryObject () {
  let search = window.location.search.substr(1) || window.location.hash.split('?')[1], // 获取url的参数部分,兼容 hash(#) 模式
    obj = {};
  if (!search) return obj;
  let paramsArr = search.split('&');
  // 遍历数组
  for (let i of paramsArr) {
    let arr = i.split('=');
    obj[arr[0]] = arr[1]; // 设置对象key,value
  }
  return obj;
}

举个栗子 → 🙌🌰:www.baidu.com?id=1&type=2

formatParamsToObject() // {id: "1", type: "2"}

2. 将对象转换成Url需要的参数 tag标记是否带问号(?)


function formatObjToParamStr(obj, tag = true) {
    let data = [],
        dStr = '';
    for (let key in obj) {
        data.push(`${key}=${obj[key]}`);
    }
    dStr = tag ? '?' + data.join('&') : data.join('&');
    return dStr
}

举个栗子 → 🙌🌰:

formatObjToParamStr({id:1,type:2}) // "?id=1&type=2"
formatObjToParamStr({id:1,type:2},false) // "id=1&type=2"

3. 通过参数名获取url中的参数值


function getUrlParam(name,url) {
    let search = url || window.location.search,
        reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
        r = search.substr(search.indexOf('\?') + 1).match(reg);
    return r != null ? r[2] : '';
}

举个栗子 → 🙌🌰:www.baidu.com?id=1&type=2

getUrlParam('id','www.baidu.com?id=1&type=2') // 1

4. 设置cookie,设置max-age 属性指定cookie 的有效期(秒)


function setCookie(name, value, expiretime) {
    let cookie = `${name}=${encodeURIComponent(value)}; path=/`;
    if (typeof expiretime === 'number')cookie += `; max-age=${(60*60*24*expiretime)}`;
    document.cookie = cookie;
}

举个栗子 → 🙌🌰:

setCookie('id',1,1)
document.cookie //"id=1"

5. 读取cookie,将设置的cookie值拿到单个key 对应的值


function getCookie(name) {
    let cookie = document.cookie;
    let arrCookie = cookie.split('; ');
    for (let i = 0; i < arrCookie.length; i++) {
        let arr = arrCookie[i].split('=');
        if (arr[0] == name) return arr[1];
    }
}

举个栗子 → 🙌🌰

getCookie('id') // 1

6. 删除对应设置的cookie

max-age为0时,删除cookie


function deleteCookie(name) {
    let currentCookie = getCookie(name);
    if (currentCookie) document.cookie = name + '=' + currentCookie + `; max-age=0}; path=/`;
}

举个栗子 → 🙌🌰

deleteCookie('id')
document.cookie // ''

7. 防抖函数的应用


在一定的时间内,多次执行同一个函数,只会触发一次

function debounce(fn,delay) {
    let timer = null;
    return function () {
        if(timer) clearTimeout(timer);
        timer = setTimeout(fn,delay)
    }
}

8. 节流函数的应用


在一定时间内,多次执行同一个函数,只有第一次执行才会触发。

function throttle(fn,delay) {
    let flag = true;
    return function () {
        if(!flag) return false;
        flag = false;
        setTimeout(()=> {
           fn();
           flag = false;
        },delay);
    }
}

举个栗子 → 🙌🌰
场景:以一个输入框为例,监听鼠标弹起事件,在1s时间内, 输出时间戳,多次输入,只会执行一次。

let ele = document.getElementsByTagName('input')[0];
ele.addEventListener('keyup',throttle(()=>{
    console.log(Date.now());
},1000));

9. 正则匹配手机号码


function checkPhone(phone) {
   return /^1[3-9]\d{9}$/.test(phone);
}

举个栗子 → 🙌🌰

checkPhone(18900008888) // true 此号码随机写的,如可以拨打,告知修改

10. 正则匹配固定电话号码


function checkTel (tel) {
   return /^((d{3,4})|d{3,4}-|s)?d{5,14}$/.test(tel)
}

举个栗子 → 🙌🌰

checkTel('12306')    // true 12306服务热线
checkTel('95105105') // true 12306 订票热线
checkTel('0755-12306') //true

11. 是否是数组


function isArray (val) {
   return Object.prototype.toString.call(val) === '[object Array]';
}

举个栗子 → 🙌🌰

isArray([]) // true
isArray({}) // false

12. 是否是对象


function isObject(val) {
   return Object.prototype.toString.call(val) === '[object Object]';
}

举个栗子 → 🙌🌰

isObject([]) // false
isObject({}) // true

13. 是否是数值


function isNumber(val) {
   return Object.prototype.toString.call(val) === '[object Number]';
}

举个栗子 → 🙌🌰

isNumber(12) // true
isNumber({}) // false

14. 检测对象是否含有某个属性


function checkObjHasAtrr(obj, key) {
   return Object.prototype.hasOwnProperty.call(obj, key);
}

举个栗子 → 🙌🌰

checkObjHasAtrr({id: 1, type: 2}, 'id') // true

15.数组最大值


function max (arr) {
   if (!isArray(arr) && arr.length) return;
   return Math.max.apply(null,arr);
}

举个栗子 → 🙌🌰

max([1,2,3,4,5,6])  // 6

16. 求数组最小值


function min(arr) {
   if (!isArray(arr) && arr.length) return;
   return Math.min.apply(null, arr);
}

举个栗子 → 🙌🌰

min([1,2,3,4,5,6])  // 1

17. 生成一个新数组,该数组从start 开始,默认值为0


function toArray (list, start) {
    start = start || 0;
    let i = list.length - start;
    let ret = new Array(i);
    while (i--) {
      ret[i] = list[i + start];
    }
    return ret;
}

举个栗子 → 🙌🌰

toArray([1,2,3,4,5,6], 2) // [3, 4, 5, 6]

18. 生成随机范围的随机数[min,max]


说明:
Math.floor:下取整
Math.random:生成0~1 的随机数

function getRandom(min,max) {
   return Math.floor(Math.random() * (max - min + 1)) + min;
}

举个栗子 → 🙌🌰

getRandom(1,2) // 1 随机生成[1,2]

19. 去除字符串空格


去除首尾空格

function trim1(str) {
    return str.replace(/(^\s*)|(\s*$)/g, '');
}

去除字符串所有空格

function trim2(str) {
    return str.replace(/(\s+)/g, ''); 
}

举个栗子 → 🙌🌰

trim1(' web api ') // 'web api'
trim2(' web api ') // 'webapi'

20. 阻止默认事件操作


preventDefault用于取消一个目标元素的默认行为。默认事件,比如a标签,点击默认跳转。

function preventDefault(e) {
    e = e || window.event;
    if (e & e.preventDefault) e.preventDefault();
    else e.returnValue  = false; //IE低版本
}

举个栗子 → 🙌🌰
鼠标点击右键,阻止默认事件(oncontextmenu)弹起

document.oncontextmenu  = function (e) {
    preventDefault(e);
}

21. 阻止冒泡事件操作


事件冒泡:如在一个按钮是绑定一个”click”事件,那么”click”事件会依次在它的父级元素中被触发 。

function stopPropagation(e) {
    e = e || window.event;
    if (e & e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
}

举个栗子 → 🙌🌰
已input,body为栗:

let btn = document.querySelector('input');
let oBody = document.querySelector('body');
btn.onclick = function (e) {
    stopPropagation(e); // 1
    // stopPropagation(e); // 1,2
    console.log(1)
}
oBody.onclick = function () {
    console.log(2);
}

22. 将对象数据转换需要的数组


function formatObjToArr(obj) {
    if (!isObject(obj)) return [];
    let options = [];
    for (let i in obj) options.push({
        name: obj[i],
        key: i
    });
    return options;
}

举个栗子 → 🙌🌰

formatObjToArr({1: 'Jack', 2: 'Tom'}) // [{name: "Jack", key: "1"},{name: "Tom", key: "2"}

23. 删除数组中的某个元素


function removeArr(arr, val) {
    let index = arr.indexOf(val);
    if (index > -1) arr.splice(index, 1);
    return arr;
}

举个栗子 → 🙌🌰

removeArr([1,2,3,4,5,6,7,8],4) //  [1, 2, 3, 5, 6, 7, 8]

24. 数组去重


function uniqueArr(arr) {
    return Array.from(new Set(arr));
}

举个栗子 → 🙌🌰

uniqueArr([1, 2, 1, 3]) //[1, 2, 3]

25. 图片下载


注: 在微信自带的游览器中不支持,微信会拦截,可以使用微信的JS-SDK。 服务器端需要设置允许跨域:access-control-allow-origin: *

function downImage(imageSrc, name) {
    let image = new Image();
    // 处理canvas 跨域问题 
    image.setAttribute('crossOrigin', 'anonymous');
    image.onload = function() {
        let canvas = document.createElement('canvas');
        let context = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;
        context.drawImage(image, 0, 0, image.width, image.height);
        let url = canvas.toDataURL('image/png'); // 图片编码数据
        let a = document.createElement('a');
        let event = new MouseEvent('click'); // 创建一个单击事件
        a.download = name || 'image'; // 设置图片名称
        a.href = url; // 将生成的URL设置为a.href属性
        a.dispatchEvent(event); // 触发a的单击事件
        a = null,canvas = null;
    }
    image.src = imageSrc;
}

举个栗子 → 🙌🌰

downImage('http://n.sinaimg.cn/photo/transform/700/w1000h500/20201106/1ad8-kcpxnwv8925892.png')

26. js 深度拷贝


function deepCopy(obj, cache) {
    if (cache === void 0) cache = [];
    if (obj === null || typeof obj !== 'object') {
        return obj
    }
    let copy = Array.isArray(obj) ? [] : {};
    // 设置缓存,用于下面递归引用
    cache.push({
        original: obj,
        copy: copy
    });
    Object.keys(obj).forEach(function(key) {
        copy[key] = deepCopy(obj[key], cache);
    });
    return copy
}

举个栗子 → 🙌🌰

let obj = {
    id: 1,
    name: 'fishStudy520',
    data: [],
    getName: function() {
        return this.name
    }
}
deepCopy(obj); 

27. 获取验证码倒计时


function getCode(time) {
    let setInter = null,
        codeText = '';
    setInter = setInterval(() => {
        if (time < 1) {
            clearInterval(setInter);
            codeText = '获取验证码';
        } else {
            codeText = `已发送${ time }s`;
            time--;
        }       
    }, 1000);    
}

举个栗子 → 🙌🌰

getCode(5)

28. 将手机号码4-7位转换成 *


function replaceMobile(mobile) {
    return Number.prototype.toString.call(mobile).replace(/1(\d{2})\d{4}(\d{4})/g,'1$1****$2');
}

举个栗子 → 🙌🌰

replaceMobile(18000009999) //"180****9999"

29. 封装简易的ajax 请求


function request(obj) {
    return new Promise(function(resolve, reject) {
        let { url, method = 'GET', params = {}, isAsync = true } = obj;
        method = method.toUpperCase();
        let xhr = new XMLHttpRequest(); // 创建一个 XMLHttpRequest对象
        if (method === "POST") {
            xhr.open(method, url, isAsync);
            xhr.setRequestHeader('Content-type', 'application/json'); // json 数据格式(已json数据格式为例)
            xhr.send(JSON.stringify(params)); // json 字符串
        } else {
            let paramsStr = formateObjToParamStr(params);
            xhr.open(method, url + paramsStr, isAsync); //参数已url 方式传递
            xhr.send();
        }
        xhr.onreadystatechange = function() {
            if (xhr.status === 200 && xhr.readyState === 4) {
                let response = JSON.parse(xhr.responseText);
                resolve(response)
            } else if (xhr.readyState === 4) {
                reject({
                    code: xhr.status,
                    message: xhr.statusText
                })
            }
        }
    }).catch((e) => console.log(e))
}

举个栗子 → 🙌🌰

// 当前项目里创建 data.json 文件
{
    "code": 200,
    "data": [{
            "id": 1,
            "name": "JavaScript 高级程序设计第三版"
        },
        {
            "id": 2,
            "name": "JavaScript 权威指南"
        },
        {
            "id": 3,
            "name": "你不知道的JavaScript《上》"
        },
        {
            "id": 4,
            "name": "你不知道的JavaScript《中》"
        },
        {
            "id": 5,
            "name": "你不知道的JavaScript《下》"
        }
    ]
}

// 函数调用
(async function getRequestList() {
    let res = await request({
        url: 'data.json',
        method: 'GET',
    });
    console.log(res);
})();

// 直接调用
request({ url: 'data.json', method: 'GET',}).then(res=> {
    console.log(res)
})

30. 数值前面加 0


val: 数字
size: 长度

function addZero(val, size) {
    for (let i = 0, len = size - (val + '').length; i < len; i++) {
        val = '0' + val;
    };
    return val + '';
}

举个栗子 → 🙌🌰

addZero(20,5) // "00020"

最后


如果喜欢那就点个赞呗(👍👍👍)! (╯ε╰)(╯ε╰)(╯ε╰)

查看原文

风不识途 关注了用户 · 2020-11-15

FishStudy520 @fishstudy520_5c9ac771f1b4e

关注 343

风不识途 赞了文章 · 2020-11-15

javaScript 这些常用的代码块你知道多少?

前言


本文主要写的是自己常用的一些javaScript 代码块。记录常用代码,方便查阅。如发现错误,欢迎留言改正。

阅读三连:点赞(👍)、关注(😍)、收藏(📝)。

正文


1. 将Url参数转换成对象,没有参数时返回空对象


@毛宗太 在【毛宗太】的指正下,新增兼容hash(#)模式;

function getQueryObject () {
  let search = window.location.search.substr(1) || window.location.hash.split('?')[1], // 获取url的参数部分,兼容 hash(#) 模式
    obj = {};
  if (!search) return obj;
  let paramsArr = search.split('&');
  // 遍历数组
  for (let i of paramsArr) {
    let arr = i.split('=');
    obj[arr[0]] = arr[1]; // 设置对象key,value
  }
  return obj;
}

举个栗子 → 🙌🌰:www.baidu.com?id=1&type=2

formatParamsToObject() // {id: "1", type: "2"}

2. 将对象转换成Url需要的参数 tag标记是否带问号(?)


function formatObjToParamStr(obj, tag = true) {
    let data = [],
        dStr = '';
    for (let key in obj) {
        data.push(`${key}=${obj[key]}`);
    }
    dStr = tag ? '?' + data.join('&') : data.join('&');
    return dStr
}

举个栗子 → 🙌🌰:

formatObjToParamStr({id:1,type:2}) // "?id=1&type=2"
formatObjToParamStr({id:1,type:2},false) // "id=1&type=2"

3. 通过参数名获取url中的参数值


function getUrlParam(name,url) {
    let search = url || window.location.search,
        reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
        r = search.substr(search.indexOf('\?') + 1).match(reg);
    return r != null ? r[2] : '';
}

举个栗子 → 🙌🌰:www.baidu.com?id=1&type=2

getUrlParam('id','www.baidu.com?id=1&type=2') // 1

4. 设置cookie,设置max-age 属性指定cookie 的有效期(秒)


function setCookie(name, value, expiretime) {
    let cookie = `${name}=${encodeURIComponent(value)}; path=/`;
    if (typeof expiretime === 'number')cookie += `; max-age=${(60*60*24*expiretime)}`;
    document.cookie = cookie;
}

举个栗子 → 🙌🌰:

setCookie('id',1,1)
document.cookie //"id=1"

5. 读取cookie,将设置的cookie值拿到单个key 对应的值


function getCookie(name) {
    let cookie = document.cookie;
    let arrCookie = cookie.split('; ');
    for (let i = 0; i < arrCookie.length; i++) {
        let arr = arrCookie[i].split('=');
        if (arr[0] == name) return arr[1];
    }
}

举个栗子 → 🙌🌰

getCookie('id') // 1

6. 删除对应设置的cookie

max-age为0时,删除cookie


function deleteCookie(name) {
    let currentCookie = getCookie(name);
    if (currentCookie) document.cookie = name + '=' + currentCookie + `; max-age=0}; path=/`;
}

举个栗子 → 🙌🌰

deleteCookie('id')
document.cookie // ''

7. 防抖函数的应用


在一定的时间内,多次执行同一个函数,只会触发一次

function debounce(fn,delay) {
    let timer = null;
    return function () {
        if(timer) clearTimeout(timer);
        timer = setTimeout(fn,delay)
    }
}

8. 节流函数的应用


在一定时间内,多次执行同一个函数,只有第一次执行才会触发。

function throttle(fn,delay) {
    let flag = true;
    return function () {
        if(!flag) return false;
        flag = false;
        setTimeout(()=> {
           fn();
           flag = false;
        },delay);
    }
}

举个栗子 → 🙌🌰
场景:以一个输入框为例,监听鼠标弹起事件,在1s时间内, 输出时间戳,多次输入,只会执行一次。

let ele = document.getElementsByTagName('input')[0];
ele.addEventListener('keyup',throttle(()=>{
    console.log(Date.now());
},1000));

9. 正则匹配手机号码


function checkPhone(phone) {
   return /^1[3-9]\d{9}$/.test(phone);
}

举个栗子 → 🙌🌰

checkPhone(18900008888) // true 此号码随机写的,如可以拨打,告知修改

10. 正则匹配固定电话号码


function checkTel (tel) {
   return /^((d{3,4})|d{3,4}-|s)?d{5,14}$/.test(tel)
}

举个栗子 → 🙌🌰

checkTel('12306')    // true 12306服务热线
checkTel('95105105') // true 12306 订票热线
checkTel('0755-12306') //true

11. 是否是数组


function isArray (val) {
   return Object.prototype.toString.call(val) === '[object Array]';
}

举个栗子 → 🙌🌰

isArray([]) // true
isArray({}) // false

12. 是否是对象


function isObject(val) {
   return Object.prototype.toString.call(val) === '[object Object]';
}

举个栗子 → 🙌🌰

isObject([]) // false
isObject({}) // true

13. 是否是数值


function isNumber(val) {
   return Object.prototype.toString.call(val) === '[object Number]';
}

举个栗子 → 🙌🌰

isNumber(12) // true
isNumber({}) // false

14. 检测对象是否含有某个属性


function checkObjHasAtrr(obj, key) {
   return Object.prototype.hasOwnProperty.call(obj, key);
}

举个栗子 → 🙌🌰

checkObjHasAtrr({id: 1, type: 2}, 'id') // true

15.数组最大值


function max (arr) {
   if (!isArray(arr) && arr.length) return;
   return Math.max.apply(null,arr);
}

举个栗子 → 🙌🌰

max([1,2,3,4,5,6])  // 6

16. 求数组最小值


function min(arr) {
   if (!isArray(arr) && arr.length) return;
   return Math.min.apply(null, arr);
}

举个栗子 → 🙌🌰

min([1,2,3,4,5,6])  // 1

17. 生成一个新数组,该数组从start 开始,默认值为0


function toArray (list, start) {
    start = start || 0;
    let i = list.length - start;
    let ret = new Array(i);
    while (i--) {
      ret[i] = list[i + start];
    }
    return ret;
}

举个栗子 → 🙌🌰

toArray([1,2,3,4,5,6], 2) // [3, 4, 5, 6]

18. 生成随机范围的随机数[min,max]


说明:
Math.floor:下取整
Math.random:生成0~1 的随机数

function getRandom(min,max) {
   return Math.floor(Math.random() * (max - min + 1)) + min;
}

举个栗子 → 🙌🌰

getRandom(1,2) // 1 随机生成[1,2]

19. 去除字符串空格


去除首尾空格

function trim1(str) {
    return str.replace(/(^\s*)|(\s*$)/g, '');
}

去除字符串所有空格

function trim2(str) {
    return str.replace(/(\s+)/g, ''); 
}

举个栗子 → 🙌🌰

trim1(' web api ') // 'web api'
trim2(' web api ') // 'webapi'

20. 阻止默认事件操作


preventDefault用于取消一个目标元素的默认行为。默认事件,比如a标签,点击默认跳转。

function preventDefault(e) {
    e = e || window.event;
    if (e & e.preventDefault) e.preventDefault();
    else e.returnValue  = false; //IE低版本
}

举个栗子 → 🙌🌰
鼠标点击右键,阻止默认事件(oncontextmenu)弹起

document.oncontextmenu  = function (e) {
    preventDefault(e);
}

21. 阻止冒泡事件操作


事件冒泡:如在一个按钮是绑定一个”click”事件,那么”click”事件会依次在它的父级元素中被触发 。

function stopPropagation(e) {
    e = e || window.event;
    if (e & e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
}

举个栗子 → 🙌🌰
已input,body为栗:

let btn = document.querySelector('input');
let oBody = document.querySelector('body');
btn.onclick = function (e) {
    stopPropagation(e); // 1
    // stopPropagation(e); // 1,2
    console.log(1)
}
oBody.onclick = function () {
    console.log(2);
}

22. 将对象数据转换需要的数组


function formatObjToArr(obj) {
    if (!isObject(obj)) return [];
    let options = [];
    for (let i in obj) options.push({
        name: obj[i],
        key: i
    });
    return options;
}

举个栗子 → 🙌🌰

formatObjToArr({1: 'Jack', 2: 'Tom'}) // [{name: "Jack", key: "1"},{name: "Tom", key: "2"}

23. 删除数组中的某个元素


function removeArr(arr, val) {
    let index = arr.indexOf(val);
    if (index > -1) arr.splice(index, 1);
    return arr;
}

举个栗子 → 🙌🌰

removeArr([1,2,3,4,5,6,7,8],4) //  [1, 2, 3, 5, 6, 7, 8]

24. 数组去重


function uniqueArr(arr) {
    return Array.from(new Set(arr));
}

举个栗子 → 🙌🌰

uniqueArr([1, 2, 1, 3]) //[1, 2, 3]

25. 图片下载


注: 在微信自带的游览器中不支持,微信会拦截,可以使用微信的JS-SDK。 服务器端需要设置允许跨域:access-control-allow-origin: *

function downImage(imageSrc, name) {
    let image = new Image();
    // 处理canvas 跨域问题 
    image.setAttribute('crossOrigin', 'anonymous');
    image.onload = function() {
        let canvas = document.createElement('canvas');
        let context = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;
        context.drawImage(image, 0, 0, image.width, image.height);
        let url = canvas.toDataURL('image/png'); // 图片编码数据
        let a = document.createElement('a');
        let event = new MouseEvent('click'); // 创建一个单击事件
        a.download = name || 'image'; // 设置图片名称
        a.href = url; // 将生成的URL设置为a.href属性
        a.dispatchEvent(event); // 触发a的单击事件
        a = null,canvas = null;
    }
    image.src = imageSrc;
}

举个栗子 → 🙌🌰

downImage('http://n.sinaimg.cn/photo/transform/700/w1000h500/20201106/1ad8-kcpxnwv8925892.png')

26. js 深度拷贝


function deepCopy(obj, cache) {
    if (cache === void 0) cache = [];
    if (obj === null || typeof obj !== 'object') {
        return obj
    }
    let copy = Array.isArray(obj) ? [] : {};
    // 设置缓存,用于下面递归引用
    cache.push({
        original: obj,
        copy: copy
    });
    Object.keys(obj).forEach(function(key) {
        copy[key] = deepCopy(obj[key], cache);
    });
    return copy
}

举个栗子 → 🙌🌰

let obj = {
    id: 1,
    name: 'fishStudy520',
    data: [],
    getName: function() {
        return this.name
    }
}
deepCopy(obj); 

27. 获取验证码倒计时


function getCode(time) {
    let setInter = null,
        codeText = '';
    setInter = setInterval(() => {
        if (time < 1) {
            clearInterval(setInter);
            codeText = '获取验证码';
        } else {
            codeText = `已发送${ time }s`;
            time--;
        }       
    }, 1000);    
}

举个栗子 → 🙌🌰

getCode(5)

28. 将手机号码4-7位转换成 *


function replaceMobile(mobile) {
    return Number.prototype.toString.call(mobile).replace(/1(\d{2})\d{4}(\d{4})/g,'1$1****$2');
}

举个栗子 → 🙌🌰

replaceMobile(18000009999) //"180****9999"

29. 封装简易的ajax 请求


function request(obj) {
    return new Promise(function(resolve, reject) {
        let { url, method = 'GET', params = {}, isAsync = true } = obj;
        method = method.toUpperCase();
        let xhr = new XMLHttpRequest(); // 创建一个 XMLHttpRequest对象
        if (method === "POST") {
            xhr.open(method, url, isAsync);
            xhr.setRequestHeader('Content-type', 'application/json'); // json 数据格式(已json数据格式为例)
            xhr.send(JSON.stringify(params)); // json 字符串
        } else {
            let paramsStr = formateObjToParamStr(params);
            xhr.open(method, url + paramsStr, isAsync); //参数已url 方式传递
            xhr.send();
        }
        xhr.onreadystatechange = function() {
            if (xhr.status === 200 && xhr.readyState === 4) {
                let response = JSON.parse(xhr.responseText);
                resolve(response)
            } else if (xhr.readyState === 4) {
                reject({
                    code: xhr.status,
                    message: xhr.statusText
                })
            }
        }
    }).catch((e) => console.log(e))
}

举个栗子 → 🙌🌰

// 当前项目里创建 data.json 文件
{
    "code": 200,
    "data": [{
            "id": 1,
            "name": "JavaScript 高级程序设计第三版"
        },
        {
            "id": 2,
            "name": "JavaScript 权威指南"
        },
        {
            "id": 3,
            "name": "你不知道的JavaScript《上》"
        },
        {
            "id": 4,
            "name": "你不知道的JavaScript《中》"
        },
        {
            "id": 5,
            "name": "你不知道的JavaScript《下》"
        }
    ]
}

// 函数调用
(async function getRequestList() {
    let res = await request({
        url: 'data.json',
        method: 'GET',
    });
    console.log(res);
})();

// 直接调用
request({ url: 'data.json', method: 'GET',}).then(res=> {
    console.log(res)
})

30. 数值前面加 0


val: 数字
size: 长度

function addZero(val, size) {
    for (let i = 0, len = size - (val + '').length; i < len; i++) {
        val = '0' + val;
    };
    return val + '';
}

举个栗子 → 🙌🌰

addZero(20,5) // "00020"

最后


如果喜欢那就点个赞呗(👍👍👍)! (╯ε╰)(╯ε╰)(╯ε╰)

查看原文

赞 27 收藏 21 评论 6

风不识途 发布了文章 · 2020-11-14

基于React全家桶开发「网易云音乐PC」项目实战(二)

前言

本篇开始做 「网易云音乐PC」项目,建议最好有以下基础react、redux、redux-thunk、react-router上一章只是对项目进行初步介绍认识,本章节会带你完成:网易云的基本骨架结构并完成使用redux-immutable重构redux

<details>
<summary>本章节完成结果如下</summary>

<img data-original="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f6680e5c03844424827672caa46dabba~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />

</details>

项目初始化

前言-vscode&chrome插件(可选)

  • 如果已经安装过了可以选择跳过,以下都是可选的,当然不安装也没问题
  • 为了更便捷的开发项目,推荐安装以下vscode插件

    • ESLint: 代码风格检查工具,帮助我们规范代码书写
    • vscode-styled-components: 在编写styled-components中语法高亮显示和样式组件的
    • path-alias: 别名路径有对应的智能提示
    • ES7 React/Redux/GraphQL/React-Native snippets: 代码片段
  • chrome插件

1.项目目录划分

  • 使用create-react-app脚手架初始化项目结构: create-react-app music163_xxx
  • 目录结构也可以按照自己习惯的结构来划分
│─src
  ├─assets 存放公共资源css和图片
    ├─css  全局css
    ├─img  
  ├─common  公共的一些常量
  ├─components 公共组件
  ├─pages   路由映射组件
  ├─router  前端路由配置
  ├─service 网络配置和请求
  └─store   全局的store配置
  └─utils   工具函数
  └─hooks   自定义hook

2.项目样式选择

  • 项目样式重置选择:

    • [ ] reset.css
    • [x] normalize.css + custom.css(也就是自定义的css)
  • 安装normalize.css: yarn add normalize.css

    • 在全局css文件引入: src->assets->css-> normalize.css
    • 首先下载项目资源(都是项目使用到的一些背景图和精灵图)

      • 如果下载github文件慢,参考我的这篇文章加速🚀加载文件
    • 下面的全局CSS是用于页面初始化,如果你的css掌握的不错,那么建议直接拷贝😏

      • 将下面👇css拷贝到全局自定义的css文件当中(src -> assets -> css -> reset.css)
      • 定义的挺多的都是一些精灵图背景
      • 精灵图的类名都是对应的图片文件名
/* reset.css (自定义的css) */
@import '~normalize.css';
/* 后续有说明,先跳过即可(安装完antd再导入的) */
/* @import '~antd/dist/antd.css'; */

/* 样式的重置 */
body, html, h1, h2, h3, h4, h5, h6, ul, ol, li, dl, dt, dd, header, menu, section, p, input, td, th, ins {
  padding: 0;
  margin: 0;
}

ul, ol, li {
  list-style: none;
}

a {
  text-decoration: none;
  color: #666;
}

a:hover {
  color: #666;
  text-decoration: underline;
}

i, em {
  font-style: normal;
}

input, textarea, button, select, a {
  outline: none;
  border: none;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

img {
  border: none;
  vertical-align: middle;
}

/* 全局样式 */
body, textarea, select, input, button {
  font-size: 12px;
  color: #333;
  font-family: Arial, Helvetica, sans-serif;
  background-color: #f5f5f5;
}

.text-nowrap {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.w1100 {
  width: 1100px;
  margin: 0 auto;
}

.w980 {
  width: 980px;
  margin: 0 auto;
}

.text-indent {
  text-indent: -9999px;
}

.inline-block {
  display: inline-block;
}

.sprite_01 {
  background: url(../img/sprite_01.png) no-repeat 0 9999px;
}

.sprite_02 {
  background: url(../img/sprite_02.png) no-repeat 0 9999px;
}

.sprite_cover {
  background: url(../img/sprite_cover.png) no-repeat 0 9999px;
}

.sprite_icon {
  background: url(../img/sprite_icon.png) no-repeat 0 9999px;
}

.sprite_icon2 {
  background: url(../img/sprite_icon2.png) no-repeat 0 9999px;
}

.sprite_button {
  background: url(../img/sprite_button.png) no-repeat 0 9999px;
}

.sprite_button2 {
  background: url(../img/sprite_button2.png) no-repeat 0 9999px;
}

.sprite_table {
  background: url(../img/sprite_table.png) no-repeat 0 9999px;
}

.my_music {
  background: url(../img/mymusic.png) no-repeat 0 9999px;
}

.not-login {
  background: url(../img/notlogin.jpg) no-repeat 0 9999px;
}

.image_cover {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  text-indent: -9999px;
  background: url(../img/sprite_cover.png) no-repeat -145px -57px;
}

.sprite_player {
  background: url(../img/playbar_sprite.png) no-repeat 0 9999px;
}

.lyric-css .ant-message-notice-content {
  position: fixed;
  left: 50%;
  bottom: 50px;
  transform: translateX(-50%);
  background-color: rgba(0,0,0,.5);
  color: #f5f5f5;
}

.wrap-bg2 {
  background: url(../img/wrap3.png) repeat-y center 0;;
}

3.配置路径别名

  • 第一步:安装craco:

    • yarn add @craco/craco
  • 第二步:修改package.json文件

    • 原本启动时,我们是通过react-scripts来管理的;
    • 现在启动时,我们通过craco来管理;
    "scripts": {
    -"start": "react-scripts start",
    -"build": "react-scripts build",
    -"test": "react-scripts test",
    
    \+ "start": "craco start",
    \+ "build": "craco build",
    \+ "test": "craco test",
    }
  • 第三步:在根目录下创建 craco.config.js 文件用于修改默认配置↓

    • module.exports = { // 配置文件 }
// 根路径 -> craco.config.js
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)

module.exports = {
  webpack: {
    alias: {
       // @映射src路径
      '@': resolve('src'),
      'components': resolve('src/components')
    }
  }
}

项目结构划分

header组件

  • 状态:固定,不会随着URL发生变化
  • 组件存放:src/"components/app-header"文件夹中
  • 先点击查看要完成效果

footer组件

  • 状态:固定,不会随着URL发生变化
  • 组件存放:src/"components/app-footer"文件夹中
  • 先点击查看要完成效果

main主体内容

  • 主体内容会是随着路径变化动态的发生改变的
  • 使用router动态渲染path对应的组件,具体配置如下↓

    • 前提: 在src/pages文件夹有创建discover和mine和friend组件
  1. 安装routeryarn add react-router-dom
  2. 集中式配置路由映射:yarn add react-router-config

    // src/router->index.js  (配置路由映射)
    import { Redirect } from "react-router-dom";
    import Discover from "@/pages/discover";
    import Mine from "@/pages/mine";
    import Friend from "@/pages/friend";
    
    const routes = [
      {
        path: "/discover",
        component: Discover    
      },
      {
        path: "/mine",
        component: Mine
      },
      {
        path: "/friend",
        component: Friend
      },
    ];
    
    export default routes;
  3. App.js使用HashRouter组件包裹使用router-config配置的路由映射(使路由映射表的配置生效):

    • <details>
      <summary>点击查看 App.js 中的配置</summary>

        <img data-original="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ddc98a03ea644a8ca1c06c3f3e2db678~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />

      </details>

    • 验证路由是否配置成功:在header组件中,使用NavLink测试路径切换,渲染对应组件
    • 点击查看完成效果

完成效果如下👇

  • 主体内容跟随URL发生变化,注意路径的变化和组件的切换

<br/>
<br/>

Header头部组件

1.头部组件样式编写

  • 为了防止多个组件中样式冲突, 组件内使用样式styled-components
  • 安装使用: yarn add styled-components
  • 布局使用: Flex

2.头部区域划分

3.头部区域实现及思路(左)

header-item-active

实现功能:点击头部列表项,添加背景实现高亮和下面的小三角
实现思路:(利用`NavLink`组件被点击有`active`的`className`单独给class设置样式即可)
   1.NavLink点击活跃后实现上面的效果
   2.给NavLink设置自定义className,在对应的css文件实现效果

4.头部区域实现及思路(右)

  • 右侧可以使用Antd组件也可以自行编写
  • 安装Ant design: yarn add antd
  • 安装Ant design icons: yarn add @ant-design/icons
1.在reset.css文件引入: antd样式 ↓
    @import '~antd/dist/antd.css';
2.在Header.js引入icons
3.使用antd组件: Input组件
4.修改placehold文本样式
  • 注意:右侧的搜索的键盘图标我是后来加的,可以先暂时跳过,有兴趣的朋友可以做一下

<br/>

Footer底部组件

1.底部区域布局

2.实现效果

<br/>

路由优化和API说明

1.项目接口文档

2.路由优化_重定向

  • 对'根路由'进行重定向到: discover页面
//  src/router/router.js -> 对根路径进行重定向到: /discover   👇
const routes = [
    //   `/`根路径重定向到: /discover路径
--->{ path: '/', exact: true, render: () => <Redirect to="/discover" /> },<----
    { path: '/discover', component: JMDiscover }
  //  ...
]

3.嵌套路由

布局划分

创建对应的子组件

<details>
<summary>创建Discover文件夹下的对应的子组件</summary>

<img data-original="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0374a1352cba41ceb4ea090be4310160~tplv-k3u1fbpfcp-zoom-1.image"  />

</details>

配置"嵌套路由映射表"

const routes = [
  { path: '/', exact: true, render: () => <Redirect to="/discover" /> },
  {
    path: '/discover',
    component: JMDiscover,
--->routes: [
      { path: '/discover', render: () => <Redirect to="/discover" /> },
      { path: '/discover/recommend', component: JMRecommend },
      { path: '/discover/ranking', component: JMRanking },
      { path: '/discover/album', component: JMAlbum },
      { path: '/discover/djradio', component: JMDjradio },
      { path: '/discover/artist', component: JMArtist },
      { path: '/discover/songs', component: JMSongs }
    ],<----
  },
  { path: '/mine', component: JMMine },
  { path: '/friend', component: JMFriend },
]

渲染嵌套子路由config

  • discover页面下渲染嵌套子路由
// src->pages->discover->index.js
export default memo(function JMDiscover(props) {
  const { route } = props
    return (
    <div>
       ...
      {renderRoutes(route.routes)}
    </div>
  )
})

完成效果↓

4.轮播图API

  • 开始发送网络请求使用axios
  • 安装axios: yarn add axios

    • src文件夹下新建service文件夹📂用于网络请求
    • 前提: 将axios封装好的文件, 拷贝到该文件夹下
    • 如果以前没有封装过, 可以参考下我的axios简易封装如下👇
    • <details>
      <summary>axios简易封装,点击查看</summary>

      <img data-original="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4a971fd109094419ae0aa82b74b43573~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />

      </details>

  • 现在让我们开始请求轮播图数据:

<br/>

redux保存服务器返回的数据

1.安装redux

  • 安装:

    • yarn add redux
    • yarn add react-redux
    • yarn add redux-thunk
  • 合并安装: yarn add redux react-redux redux-thunk

<details>
<summary>组织目录织结构</summary>
<img data-original="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb44faf4501f4218b644920af81e347f~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:80%;" />
</details>

2.配置redux

项目根目录src下的store → reducer.js

import { combineReducers } from "redux";
// 引入recommend页面的store(下面可以暂时不写,跳到下第3小结)
import { reducer as recommendReducer } from '../pages/discover/child-pages/recommend/store'

// 将多个reducer合并
const cRducer = combineReducers({
   // 下面可以暂时不写(下面可以暂时不写,跳到下第3小结)
  recommend: recommendReducer
})
export default cRducer

项目根src下store → index.js

import { createStore, applyMiddleware, compose } from "redux";
// 引入thunk中间件(可以让派发的action可以是一个函数)
import thunk from 'redux-thunk'
// 引入合并后的reducer
import cRducer from "./reducer";
// redux-devtools -> 浏览器插件
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// 创建store并传递: 1.reducer(纯函数) 2.StoreEnhancer
const store = createStore(cRducer, composeEnhancers(
  applyMiddleware(thunk)
))

export default store

项目根src目录下app.js文件中 → 配置react-redux

// 在App.js组件中使用react-redux
import { Provider } from 'react-redux'
import store from './store'
export default memo(function App() {
  return (
    <Provider store={store}>
       // ...   
       {renderRoutes(routes)}
    </Provider>
  )
})

小结

  1. 创建combinReducers

    • 用于将多个reducer合并
  2. 创建store

    • 配置中间件和redux-devtools
  3. 配置react-redux

    • 帮助我们完成连接redux

3.轮播图数据通过redux-thunk来请求

  • 轮播图数据API接口:

  • 将请求下来的的轮播图数据,放到 redux 当中
  • 注意代码注释中的文件路径
// src->page->dicover->child-pages->recommend->store->actionCreator.js (派发action用的)
import * as actionTypes from './actionTypes'
import { getTopBanners } from '@/service/recommend.js'
// 轮播图Action
export const changeTopBannerAction = res => ({
  type: actionTypes.CHANGE_TOP_BANNER,
  topBanners: res,
})
// 轮播图网络请求
export const getTopBannersAction = () => {
  return dispatch => {
    // 发送网络请求
    getTopBanners().then(res => {
      dispatch(changeTopBannerAction(res))
    })
  }
}

// service->recommend.js ------推荐页的轮播图API接口-----------
import request from './request'
export function getTopBanners() {
  return request({
    url: "/banner"
  })
}

// page->dicover->child-pages->recommend.js 
import React, { memo, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getTopBannersAction } from './store/actionCreator'

function JMRecommend(props) {
  // redux Hook 组件和redux关联: 获取数据和进行操作
  const { topBanners } = useSelector(state => ({
    topBanners: state.recommend.topBanners,
  }))
  const dispatch = useDispatch()
  
  useEffect(() => {
    dispatch(getTopBannersAction())
  }, [dispatch])

  return (
    <div>
      <h2>JMRecommend</h2>
      <h3>{topBanners.length}</h3>
    </div>
  )
}
export default memo(JMRecommend)

4.useSelector性能优化

---> import { shallowEqual, useDispatch, useSelector } from 'react-redux'  <---

const { topBanners } = useSelector(state => ({
  topBanners: state.recommend.topBanners,
---> }), shallowEqual)  <---

<br/>

结合ImmutableJS

immutableJS介绍及安装

  • immutableJS介绍: 使用Immutable可以让redux中的维护的state不在是浅层拷贝再赋值, 而是使用Immutable数据结构保存数据
  • 使用immutableJS好处: 修改的state不会修改原有数据结构, 而是返回修改后新的数据结构, 可以利用之前的数据结构而不会造成内存的浪费
  • immutableJS安装: yarn add immutable

结合Redux管理数据

  1. 使用redux-immutable中的combineReducers;
  2. 所有的reducer中的数据都转换成Immutable类型的数据

immutableJS融入项目

<details>
<summary>对项目当前目录reducer使用ImmutableJS</summary>
<img data-original="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5aa0b42d08684a60bd905ee468283cb2~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20200927215952708" />
</details>

// 1.在reducer.js文件使用Immutable设置: discover->child-cpn->recommend->store->reducer.js
/* --> */ import { Map } from "immutable";  //<---
import * as actionTypes from './actionTypes'

// 使用Immutable管理redux中的state (修改的`state`不会修改原有数据结构, 而是返回修改后新的数据结构)
const defaultState = Map({
  topBanners: [],
})

export default function reducer(state = defaultState, action) {
  switch (action.type) {
    case actionTypes.CHANGE_TOP_BANNER:
/* ---> */   return state.set('topBanners', action.topBanners)  //<---
    default:
      return state
  }
}


// 2.在recommend的index.js文件获取的是Immutable对象, 需要进行设置
const { topBanners } = useSelector(state => ({
--->   topBanners: state.recommend.get('topBanners')  <---
  }))

redux-Immutable

  • 为什么不对项目根目录的reducer使用immutableJS?

    • 因为根目录的reducer是将多个reducer进行合并的
    • 会非常频繁的操作reducer, 而且使用对combineRducer返回的对象使用immutable进行管理是不能合并的
  • 使用redux-immutable:

    • 安装: yarn add redux-immutable
// 根目录下src->store->reducer
import { combineReducers } from 'redux-immutable'

import { reducer as recommendReducer } from '../pages/discover/child-pages/recommend/store'

// 多个reducer合并
const cRducer = combineReducers({
  recommend: recommendReducer
})

export default cRducer
  • recommend.js文件中修改获取state方式

    • 因为原有stateimmutable对象, 所以需要修改原有获取方式
// 在recommend👉c-cpns👉top-banners👉index.js文件 (获取的是Immutable对象, 需要进行设置)
const { topBanners } = useSelector(state => ({
    // 下面两行获取state方式相等
    // topBanners: state.get('recommend').get('topBanners')
--> topBanners: state.getIn(['recommend', 'topBanners']) <--
  }))

推荐页Banner

1.轮播图区域布局


  • 轮播图组件布局: TopBanner

  • 轮播图采用antd走马灯组件↓

2.使用antd走马灯组件

3.背景高斯模糊实现

  • 在我们在网易云音乐官网,切换轮播图的时候,会发现轮播图在切换的时候会有渐变效果
  • 如何实现渐变效果:其实就是一张背景图片, 只不是在请求背景图url添加了其他的参数

添加高斯模糊背景

  • 我们在网易云官网发现其实背景图只是添加了:

    • 查询字符串也就是query string参数( ?imageView&blur=40x20 )

网易云音乐 高斯模糊背景图?imageView&blur=40x20

    • 只需要给Banner元素传递背景图片URL,通过属性穿透在style.js中获取URL显示即可 (可以先给banner传递一个固定的高斯模糊背景图)
    • 实现思路:

      1. 监听轮播图的切换 走马灯组件有对应的APIbeforeChange, 切换面板前的回调
      2. 并使用use Callback将事件函数包裹

        • <details>
          <summary>如果不了解该Hook点我</summary>
          解决了: 当父组件的其他state发生了改变, 该事件函数没有变化, 却被重新定义了的问题(简单总结)
          </details>
        • 定义组件的currentIndex用于记录, 幻灯片切换的索引
      3. 在事件函数参数有from to

        • (to参数是Carousel走马灯传递函数的参数)
        • 我们这里使用to变量作为下一个切换的索引
      4. 根据cureent index下标来取出: redux中保存的top Banners数组对应的背景图片
      const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")

    实现效果

    banner-switch

    查看原文

    赞 0 收藏 0 评论 0

    风不识途 发布了文章 · 2020-11-11

    🔥基于React全家桶开发「网易云音乐PC」项目实战(一)

    网易云音乐PC项目实战

    项目简介

    1.项目介绍

    • 项目使用到的技术栈

      • CSS使用Flex进行布局
      • 配置路径别名使用: carco
      • 项目路由使用: react-router来管理
      • 使用react-router-config集中式路径映射表管理
      • 使用styled-components+普通的css编写样式
      • 使用axios发送网络请求
      • 项目全面拥抱React Hooks
      • 项目组件库使用: ant design
      • 使用immutable对项目reducerstate进行管理
      • 使用redux-immtable对根目录reducerstate进行管理
      • 项目使用redux-thunk中间件
      • 使用propType校验props类型及默认值
      • 使用react-transition-group添加过渡动画效果
      • 项目中的优化: 函数式组件全部采用memo、路由懒加载、函数防抖
    • 项目的目标

      • 使用React全家桶开发网易云音乐PC网站

    2.适合人群及收获

    • 适合人群:

      • 适合想了解一个项目的大致流程
      • 或者是学习了React 全家桶但是缺乏React项目经验

    • 收获:

      • 如何设计音乐播放器组件,歌词解析等
      • 项目目录的结构划分,大型项目的state管理
      • 项目的大致流程,如何进行性能优化等等
    • 注意:

      • 学习本篇文章时,页面逻辑js不再贴出 (只有大致的实现思路)
      • 毕竟思考和多动手才是实践😎

    3.页面效果和功能展示

    推荐/新碟上架/榜单

    路由切换

    歌曲评论

    排行榜

    播放器

    歌曲切换(随机、顺序、单曲循环)

    歌曲搜索

    • 新增:键盘事件↓ & 函数防抖

      • ctrl+k 搜索框获取焦点 & 唤醒搜索下拉框
      • esc 取消焦点 & 下拉框
      • enter 进入歌曲搜索详情

    歌曲搜索详情列表

    • 在搜索框中按下回车即可,搜索列表基本功能实现

    4.项目源码及API接口

    5.项目规范

    • 项目规范:项目中有一些开发规范和代码风格 (也可以按照自己的习惯)

      1. 文件夹、文件名称统一小写、多个单词以连接符(-)连接;
      2. JavaScript变量名称采用小驼峰标识,常量全部使用大写字母,组件采用大驼峰;
      3. CSS采用普通CSSstyled-component结合来编写

        • 全局采用普通CSS、局部采用styled-component
      4. 整个项目不再使用class组件,统一使用函数式组件,并且全面拥抱Hooks
      5. 所有的函数式组件,为了避免不必要的渲染,全部使用memo进行包裹;
      6. 组件内部的状态,使用useStateuseReducer;业务数据全部放在redux中管理;
      7. 函数组件内部基本按照如下顺序编写代码:

        • 组件内部state管理;
        • reduxhooks代码;
        • 其他组件hooks代码;
        • 其他逻辑代码;
        • 返回JSX代码;
      8. redux代码规范如下:

        • 每个模块有自己独立的reducer,通过combineReducer进行合并;
        • 异步请求代码使用redux-thunk,并且写在actionCreators中;
        • redux直接采用redux hooks方式编写,不再使用connect
    • 其他规范在项目中根据实际情况决定和编写;

    6.React devtools标记隐藏(了解即可)

    // disable react-dev-tools for this project 
    if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === "object") {
        for (let [key, value] of Object.entries(window.__REACT_DEVTOOLS_GLOBAL_HOOK__)) {
            window.__REACT_DEVTOOLS_GLOBAL_HOOK__[key] = typeof value == "function" ? ()=>{} : null;
        }
    }

    项目初始化

    前言-vscode&chrome插件(可选)

    • 如果已经安装过了可以选择跳过,以下都是可选的,当然不安装也没问题
    • 为了更便捷的开发项目,推荐安装以下vscode插件

      • ESLint: 代码风格检查工具,帮助我们规范代码书写
      • vscode-styled-components: 在编写styled-components中语法高亮显示和样式组件的
      • path-alias: 别名路径有对应的智能提示
      • ES7 React/Redux/GraphQL/React-Native snippets: 代码片段
    • chrome插件

    1.项目目录划分

    • 使用create-react-app脚手架初始化项目结构: create-react-app music163_xxx
    • 目录结构也可以按照自己习惯的结构来划分
    │─src
      ├─assets 存放公共资源css和图片
        ├─css  全局css
        ├─img  
      ├─common  公共的一些常量
      ├─components 公共组件
      ├─pages   路由映射组件
      ├─router  前端路由配置
      ├─service 网络配置和请求
      └─store   全局的store配置
      └─utils   工具函数
      └─hooks   自定义hook

    2.项目说明

    • 本章只是对项目进行了初步介绍及明确目标。
    • 之后关于项目实战的文章,在后续进行发布(比较详细,每章都有明确完成目标的gif图)
    • 做项目实战+开发文档不容易, 前前后后在校接近做了2个月左右, 对各位朋友有用的话, 希望给个star⭐

    感谢

    • 非常感谢王红元老师的React核心技术实战让我学习到很多 React 的知识。
    • 非常感谢后台提供者Binaryify,接口很稳定,文档很完善
    查看原文

    赞 0 收藏 0 评论 0

    认证与成就

    • 获得 3 次点赞
    • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 2020-01-26
    个人主页被 369 人浏览