Pratique de la programmation fonctionnelle du développement front-end



1. Introduction à la programmation fonctionnelle


Scénarios d'application courants

1. Fonctions telles que mapper, filtrer et réduire dans ES6
  
  
  
  
  
[1,2,3,4,5].map(x => x * 2).filter(x => x > 5).reduce((p,n) => p + n);
2. Composants de classe React -> composants fonctionnels + crochets, API combinées dans Vue3
3. Bibliothèques JS telles que RxJS, Lodash et Ramda
4. Middleware/plug-ins, tels que l'implémentation du middleware applyMiddleware dans Redux
  
  
  
  
  
const store = applyMiddleware(...middlewares)(createStore)(reducer, initialState)

Qu'est-ce que la programmation fonctionnelle
La programmation fonctionnelle est un paradigme de programmation qui résume les programmes en fonctions et en structures de données, implémente des fonctions de programme via des appels de fonction et les fonctions peuvent être transmises en tant que paramètres à d'autres fonctions.
En JavaScript, la programmation fonctionnelle peut implémenter certaines fonctionnalités de la programmation orientée objet, telles que l'abstraction, l'encapsulation, l'héritage et le polymorphisme.
Il peut également implémenter la puissance de la programmation fonctionnelle en utilisant des fonctions d'ordre supérieur, le curry, la composition et l'évaluation paresseuse.

Quelles sont les caractéristiques de la programmation fonctionnelle

Les fonctions sont des "citoyens de première classe"

  • Les fonctions peuvent être transmises en tant que paramètres à d'autres fonctions et peuvent également être renvoyées en tant que valeurs de retour de fonction (fonctions d'ordre supérieur).

exécution paresseuse

  • L'exécution différée fait référence à certains appels de fonction dans le code qui ne sont exécutés que lorsque leur valeur de retour est utilisée.
  • Il utilise la technologie du calcul différé, de sorte que la fonction ne sera exécutée que lorsqu'elle est appelée, au lieu d'être exécutée lorsque le code est écrit.
  • Cela améliore les performances car aucun calcul inutile n'est nécessaire.

Aucun effet secondaire (fonctions pures)

  • L'exécution de la fonction ne change pas l'état externe du programme, ce qui signifie que l'exécution de la fonction n'affecte pas les autres parties du programme.
  • Parce que ce n'est qu'un simple résultat de calcul et qu'il ne produira pas d'autres effets secondaires.


2. Concepts fonctionnels communs


Curry - curry

Currying une fonction est la technique de transformation d'une fonction qui prend plusieurs paramètres en une fonction qui prend un seul paramètre (le premier paramètre de la fonction d'origine) et renvoie une nouvelle fonction qui prend les paramètres restants et renvoie le résultat.
Expression de la fonction : f(a, b, c) => f(a)(b)(c)
Implémentation simple (les étudiants intéressés peuvent étudier l'implémentation dans les bibliothèques Lodash et Ramda)
  
  
  
  
  
// 函数柯里化function curry(fn, args){    args = args || []  return function(...params){        let _args = [...args, ...params]        if(_args.length < fn.length){            return curry(fn, _args)        }    return fn.apply(this, _args)  }}function sum(a, b, c){  return a+b+c}// 自由组合参数const currySum = curry(sum)console.log(currySum(1)(2)(3)) //6console.log(currySum(1)(2,3)) //6console.log(currySum(1,2)(3)) //6

pipe-line

La fonction pipeline pipe est une fonction de haut niveau qui accepte une série de fonctions comme paramètres, connecte les fonctions en série et utilise la sortie de l'étape précédente comme entrée de l'étape suivante étape par étape, de sorte qu'une logique métier complexe peut être traitées plus efficacement.
函数表达:pipe(f, g, t) => x => t(g(f(x)),进一步结合curry可以实现pipe(f)(g)(t) => x => t(g(f(x))
借助reduce简单实现,支持异步和非异步函数
  
  
  
  
  
export const pipe: any =    (...fns: Promise<Function>[]) =>    (input: any) =>        fns.reduce((chain: Promise<Function>, func: Function | Promise<Function> | any) => chain.then(func), Promise.resolve(input));

组合-compose

组合compose指的是将多个函数组合成一个函数,这样一个函数的输出就可以作为另一个函数的输入,从而实现多个函数的链式调用。
组合compose可以提高代码的可读性和可维护性,减少重复代码的出现,更加便捷地实现函数的复用。
函数表达:compose(f, g, t) => x => f(g(t(x)),进一步结合curry可以实现compose(f)(g)(t) => x =>  f(g(t(x))
借助reduceRight简单实现,和pipe的区别只是运算顺序不同
  
  
  
  
  
export const compose: any =    (...fns: Promise<Function>[]) =>    (input: any) =>        fns.reduceRight((chain: Promise<Function>, func: Function | Promise<Function> | any) => chain.then(func), Promise.resolve(input));


三、函数式编程实践


需求背景介绍

AgileBI在线报表是一款报表应用工具:易学易用,零代码,通过简单拖拽操作,制作中国式复杂报表,轻松实现报表的多样展示、交互分析、数据导出等需求, 在线体验
已有功能:在线报表中的每个单元格都可以配置相关的属性:比如扩展方向、父格、单元格格式、过滤条件、条件属性等
 新增需求: 需要支持批量设置单元格,其中【文本类型】单元格支持扩展方向、父格的设置; 【字段类型】、【公示类型】单元格支持所有配置;
大致设计思路
  1. 获取当前批量设置中,所有的配置项信息
  2. 为每个配置项设计一个处理器(高阶函数):主要处理【批量设置的配置信息】和【当前单元格的配置信息】合并或替换逻辑
  3. 通过管道的方式,加工每个单元格所有的配置项信息

核心实现

pipe函数
  
  
  
  
  
private pipe = (...args: any) => {    return (result: any, config?: any) => {        return args.reduce((acc: any, fn: any) => fn(acc, config), result);    };};
高阶函数处理每个配置项
  
  
  
  
  
// 扩展方向替换private handleExpand(expandConf: string) {    return (conf: any) => {        if (expandConf) {            conf.expandDirection = expandConf;        }        return conf;    };}// 父行/父列替换private handleParentCell(columnParentCell: any, rowParentCell: any) {    return (conf: any) => {        if (columnParentCell?.parentSelectType) {            conf.columnParentCell = columnParentCell;        }        if (rowParentCell?.parentSelectType) {            conf.rowParentCell = rowParentCell;        }        return conf;    };}// 条件属性追加private handleCondition(conditionBatchConf: any) {    return (conf: any) => {        conf.conditionConf = this.mergeCondition(conf?.conditionConf || [], conditionBatchConf);        return conf;    };}// 批量修改private mergeCondition(c1: any, c2: any) {    for (let j = 0; j < c1.length; j++) {        // 批量删除        if (            c1[j]?.batchFlag &&            this.batchConf.conditionConf?.find((item: any) => item.uuid === c1[j]?.uuid) &&            !c2.find((item: any) => item.uuid === c1[j]?.uuid)        ) {            c1.splice(j, 1);        }    }    for (let j = 0; j < c1.length; j++) {        for (let i = 0; i < c2.length; i++) {            // 如果字段已存在则替换            if (c2[i]?.uuid === c1[j]?.uuid) {                c1.splice(j, 1);            }        }    }    return [...c1, ...c2];}//...
组合函数,获取每个单元格的最终配置信息
  
  
  
  
  
//...let handles: Array<any> = [];if (cell?.dataConf?.cellType === "文本") {    handles = [        this.handleExpand(conf.expandDirection),        this.handleParentCell(conf.columnParentCell, conf.rowParentCell)    ];} else if (cell?.dataConf?.cellType === "字段" || cell?.dataConf?.cellType === "公式") {    handles = [        this.handleExpand(conf.expandDirection),        this.handleParentCell(conf.columnParentCell, conf.rowParentCell),        this.handleFormat(conf.dataFormatConf),        this.handleFilter(conf.cellFilterConf),        this.handleCondition(conf.conditionConf)    ];}if (handles.length > 0) {    const mergeConf = this.pipe(...handles)(JSON.parse(JSON.stringify(cell.dataConf)));    //...}


四、总结


  1. 函数式编程可以可提高代码的可重用性,减少重复代码的开发时间;
  2. 函数式编程可以提高代码的可读性,使得代码更容易理解和调试;
  3. 函数式编程可以更容易实现函数组合,以帮助提高可维护性;
  4. 组合优于继承;

-end-

本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

{{o.name}}
{{m.name}}

Je suppose que tu aimes

Origine my.oschina.net/u/4090830/blog/8816902
conseillé
Classement