React前奏,函数式编程基本概念

1,787 阅读5分钟

函数式编程基本概念

  • 写在之前,这些内容参考自O`REILLY系列图书《React学习手册》

  • 在React中,UI是用纯函数表示的,并且在构造DOM时,是以声明式(与此相对的,是命令式)的方式。而声明式的编程是函数式编程更广义的一部分。所以,先熟悉函数式的编程,很有必要。

结合了如下所述,我发现了es6中数组的新语法, 像mapfilterreduce这些,都满足了不可变性,数据转换,纯函数,高阶函数等要点,很精髓。

1,不可变性

  • 数据在应用程序中,在不改变原生数据结构的前提下,在此基础上拷贝后进行编辑。
  • 在此基础上,就会有浅拷贝和深拷贝的选择。

比如,在原有对象上添加属性,而不影响原对象

let person = {
  name: 'Beckham',
  age: 33
}

let player = (person, sex) => ({
  ...person,
  sex
})
console.log(person)
console.log(player(person, 'male'))

2,纯函数

结果只依赖输入参数

  • 函数至少接收一个参数
  • 返回一个值或其他函数
  • 不应该修改或影响传递给它的参数
  • 不会产生副作用,比如修改了全局变量,影响了程序的状态

3,数据转换

3.1,定义

  • 从其他数据源创建一个新的数据集。

3.2,作用

  • 为了使用转换后的副本

所以,mapfilterreduce这些函数都是必不可少的。

  • 基本用法

对象转为数组

let country = {
  "beijing": 10,
  "shanghai": 6,
  "shenzhen": 9
}
let obj = Object.keys(country).map(key => 
({
  name: key,
  value: country[key]
})
)
console.log(obj)

数组中,寻找最大值

let ages = [11,66,33,22,11,55]
let maxAge = ages.reduce((max, age) => {
if (max > age) {
  return max
} else {
  return age
}
}, 0)
console.log(maxAge)

数组去重,思路:有的话不变,没有的话添加。

let colors = ['red','green','blue','red','green','blue']
const distinctColor = colors.reduce((distinct, color) => (
  (distinct.indexOf(color) !== -1) ? distinct : [...distinct, color]
),
[]
)
console.log(distinctColor)

4,高阶函数

  • 定义:将函数作为参数传递,或返回一个函数

所以,数组的mapfilterreduce都是高阶函数

5,递归

  • 目的:在涉及到循环时,递归可提供一种替代性的方案
  • 在浏览器的调用堆栈中,会依次放入当前执行的函数,在出栈时,后进先出。

打印输出10~0

let countDown = (count, fn) => {
  fn(count)
  return (count > 0) ? countDown(count-1, fn) : count
}
countDown(10, count => console.log(count))

倒计时输出10~0

let countDown = (count, fn, delay = 1000) => {
  fn(count)
  return (count > 0) ? setTimeout(() => countDown(count-1, fn), delay) : count
}
countDown(10, count => console.log(count))

6,合成

6.1 定义

  • 将具体的业务逻辑拆解为小型纯函数,用户会在特定条件下合成它们,以串联或并联的方式进行调用。

6.2 目标

  • 通过整合若干简单函数,构造一个更高阶的函数

链式调用,就是合成技术之一

let template = 'hh:mm:ss tt'
let clockTime = template.replace('hh','09')
  .replace('mm','06')
  .replace('ss', '52')
  .replace('tt', 'PM')
console.log(clockTime)  // 09:06:52 PM

7,综上应用

  • 获取当前时间,实现实时时钟。

git图的原因,时间看起来加速了。实际运行代码显示是正确的。

// 计时器时间
const oneSecond = () => 1000

// 获取当前时间
const getCurrentTime = () => new Date()

// 清除控制台输出
const clear = () => console.clear()

// 控制台打印内容
const log = message => console.log(message)

// 构造一个时钟对象,包含时分秒
const abstractClockTime = date =>
    ({
        hours: date.getHours(),
        minutes: date.getMinutes(),
        seconds: date.getSeconds()
    })

// 接收一个时钟对象,
// 该方法用于指定12小时制
const civilianHours = clockTime =>
    ({
        ...clockTime,
        hours: (clockTime.hours > 12) ? clockTime.hours - 12 : clockTime.hours
    })

// 接收一个时钟对象
// 该方法用于,为时钟对象添加一个属性,用于表示am 或 pm
const appendAMPM = clockTime =>
    ({
        ...clockTime,
        ampm: (clockTime.hours >= 12) ? "PM" : "AM"
    })

/*
* @target 目标函数(本例中就是log函数)
* @time 时钟对象
* 返回的函数,会将时间发送给目标函数
* */
const display = target => time => target(time)

/*
* @format 模板字符串 "hh:mm:ss:tt"
* @time 时钟对象
* 返回的函数,将时间进行格式化
* */
const formatClock = format =>
    time =>
        format.replace("hh", time.hours)
            .replace("mm", time.minutes)
            .replace("ss", time.seconds)
            .replace("tt", time.ampm)
/*
* @key 时钟对象的属性
* @clockTime 时钟对象
* 返回的函数,将时钟对象的属性,包括时分秒,当<10时,加0
* */
const prependZero = key => clockTime =>
    ({
        ...clockTime,
        [key]: (clockTime[key] < 10) ? "0" + clockTime[key] : clockTime[key]
    })

/*
* 接收参数为多个函数,返回一个独立的函数
*   compose函数被调用,返回的独立函数进行调用时,如果不传参,
*   就以arg作为reduce的起始值,arg在使用时是undefined,
*   而一个函数在定义时,如果没有设置形参,该函数在调用时,传递的参数无效。
*   在这个栗子中,...fns为多个函数组成的数组。
*
* 也就是说,arg作为第一个函数的参数,如果该函数定义时没有指定形参,arg将被忽略,
* 第一个函数执行的结果,作为第二个函数执行的参数,依次类推。
* */
const compose = (...fns) =>
    (arg) =>
        fns.reduce(
            (composed, f) => f(composed),
            arg
        )

const convertToCivilianTime = clockTime =>
    compose(
        appendAMPM,
        civilianHours
    )(clockTime)

const doubleDigits = civilianTime =>
    compose(
        prependZero("hours"),
        prependZero("minutes"),
        prependZero("seconds")
    )(civilianTime)

/*
* compose已经被调用了,之后每隔1s,调用一次compose的执行结果
* 注意参与合成的函数的顺序。
*
* 清除打印台,获取时间,构造时钟对象,添加am或pm,12小时制,加0,格式化时分秒,发送给打印函数
* */
const startTicking = () =>
    setInterval(
        compose(
            clear,
            getCurrentTime,
            abstractClockTime,
            convertToCivilianTime,
            doubleDigits,
            formatClock("hh:mm:ss tt"),
            display(log)
        ),
        oneSecond()
    )

startTicking()