函数式编程简要概念

函数式编程中一个核心概念之一就是纯函数,如果一个函数满足一下几个条件,就可以认为这个函数是纯函数了:

  • 它是一个函数(废话);
  • 当给定相同的输入(函数的参数)的时候,总是有相同的输出(返回值);
  • 没有副作用;
  • 不依赖于函数外部状态。

当一个函数满足以上条件的时候,就可以认为这个函数是纯函数了。举个栗子:


// 非纯函数
let payload = 0;
function addOne(number) {
++payload;
return number + payload;
}
addOne(1); // 2
addOne(1); // 3
addOne(1); // 4 // 纯函数
function addOne(number) {
return number + 1;
}
addOne(1); // 2
addOne(1); // 2
addOne(1); // 2

上面两个栗子中,第一个就是典型的非纯函数,当第一次执行 addOne(1) 其返回的值是 2 没有错,但是再次执行相同函数的时候,其返回的值不再是 2 了,而是变成了 3 ,对比上面列出的满足纯函数的条件,就会发现:

  • addOne() 给定相同的输入的时候没有返回相同的输出;
  • addOne() 会产生副作用(会改变外部状态 payload);
  • addOne() 依赖的外部状态 payload

而第二个栗子就是一个纯函数,它既不依赖外部状态也不会产生副作用,且当给定相同输入的时候,总是返回相同的输出(执行任意多次 addOne(1) 总是返回 2 )。

以上对纯函数概念的一些简单理解。

React 核心理念

官方给出的 React 的定义是:

A JavaScript library for building user interfaces.

即专注于构建 View 层的一个库。React 的核心开发者之一的 Sebastian Markbåge 认为:

UI 只是把数据通过映射关系变成另一种形式的数据。给定相同的输入(数据)必然会有相同的输出(UI),即一个简单的纯函数。
React 中的函数式思想的具体体现

虽说 View 层可以当成是数据的另外一种展现形式,但在实际的 React 开发中,除了数据的展示以外,更重要的是还有数据的交互,举个栗子:


import React, { Component } from 'react';
import { fetchPosts } from 'path/to/api'; export default class PostList extends Component {
constructor() {
this.state = {
posts: [],
};
}
componentDidMount() {
fetchPosts().then(posts => {
this.setState({
posts: posts,
});
});
}
render() {
return (
<ul>
{ this.state.posts.map(post => <li key={post.id} onClick={this.toggleActive}>{ post.title }</li>) }
</ul>
);
}
toggleActive() {
//
}
}

这个一个典型的渲染列表的栗子,在这个栗子中除了渲染 PostList 外,还进行了数据的获取和事件的操作,也就意味着这个 PostList 组件不是一个「纯函数」。严格意义上来说这个组件还不是一个可复用的组件,比如说有这样一种业务场景,除了首页有 PostList 组件以外,在个人页面同样有个 PostList 组件,UI 一致但是交互逻辑不一致,这种情况下就无法复用首页的 PostList 组件了。为了解决这个问题,我们可以再次抽离一个真正意义上可复用的 View 层,它有一下几个特点:

  • 给定相同的数据(由父组件通过 props 传递给子组件且是唯一数据来源),总是渲染相同的 UI 界面;
  • 组件你内部不改变数据状态;
  • 不处理交互逻辑。

可以发现,这个上面所列出的满足纯函数的条件非常相似,这种组件才算是真正意义上的可复用的组件,好了,Talk is cheap, show me the code:


import React, { Component } from 'react';
import { fetchPosts } from 'path/to/api'; export default class PostListContainer extends Component {
constructor() {
this.state = {
posts: [],
};
}
componentDidMount() {
fetchPosts().then(posts => {
this.setState({
posts,
});
});
}
render() {
return (
<PostList posts={this.state.posts} toggleActive={this.toggleActive}></PostList>
);
}
toggleActive() {
//
}
} //
export default class PostList extends Component {
render() {
return (<ul>{ this.props.posts.map(post => <li key={post.id} onClick={this.props.toggleActive}>{ post.title }</li>) }</ul>);
}
}

通过这样改造之后,原本数据交互和 UI 展示耦合则组件就被分为了两个职责明确的新组建,即 PostListContainer 负责数据获取或点击等交互逻辑,而 PostList 则真正意义上的只负责纯粹的 View 层渲染。这种情况下的 PostListContainer 被称为 Container Component(容器组件)PostList 则被称为 Presentational Container(展示组件)。再回到刚刚所假设的业务场景下,此时可以通过创建不同的 Container Component 来处理不同的交互逻辑,然后把最终的数据通过 props 传递给子组件 PostList,这样的话不管是首页还是个人都可以真正复用 PostList 这个 Presentational Component 了。

再回过头来思考一下前面提到的 Sebastian Markbåge 所认为的理念:

UI 只是把数据通过映射关系变成另一种形式的数据。给定相同的输入(数据)必然会有相同的输出(UI),即一个简单的纯函数。

我们可以把这句话高度抽象成一个函数:data => View,拿前面的 Presentational Component PostList 来说,其中 this.props.posts 就是 data => View 中的 data,而整个渲染结果就是 View,我们再单独分析一下这个组件:


import React, { Component } from 'react'; export default class PostList extends Component {
render() {
return (<ul>{ this.props.posts.map(post => <li key={post.id} onClick={this.props.toggleActive}>{ post.title }</li>) }</ul>);
}
}

其实会发现,尽管这个组件已经很简单了,this.props.posts 传入数据,然后渲染结果(同时还有绑定事件,但是没有事件处理的具体逻辑),没有再做其他操作了。但我们仔细思考的话,还是会发现有两个比较明显的问题,一个是写法上还是典型的面向对象的方式来写的;其次是该组件内部还有 this 关键字,为什么说在这里使用关键字 this 是不合适的呢,因为 JavaScript 严格来说并不是函数式编程语言,在 JavaScriptthis 的指向又非常容易的被改变,所以依赖于 this 关键字的 data 是非常不稳定的。

好在以上两个问题再 Reactv0.14 版本中得到了解决,在此次版本中 React 有一个新的特性叫 Stateless Functional Components。什么意思呢?我们把上面的 PostList 组件以 Stateless Functional Components 的方式来重新编写就会一目了然了:


const PostList = props => (
<ul>
{ props.posts.map(post => (<li key={ post.id } onClick={ props.toggleActive }>{ post.title }</li>)) }
</ul>
); // 参数解构
const PostList = ({ posts, toggleActive }) => (
<ul>
{ posts.map(post => (<li key={ post.id } onClick={ toggleActive }>{ post.title }</li>)) }
</ul>
);

我们会发现 Stateless Functional Components 完美的诠释了前面所提到的 data => View 这个理念,不仅数据输入不依赖于 this 关键字,且书写风格也更像函数式风格。

总结

在平时的开发中,应该避免数据交互逻辑与数据渲染的过于耦合,严格区分 Container ComponentPresentational Component 的职责不仅可以更容易的复用组件,而且也容易定位问题的所在。

参考文章:

  1. Which Programming Languages Are Functional?

来源:https://segmentfault.com/a/1190000017750929

React 中的函数式思想的更多相关文章

  1. React中的响应式设计思想和事件绑定

    这两个点是react入门非常重要的两个点,以前我们是直接操作dom的形式去做,react的设计思想和以前直接操作dom是完全不同的,react是一个响应式的框架,他在做编程的时候,强调的是我们不要直接 ...

  2. C++学习35 模板中的函数式参数

    C++对模板类的支持比较灵活,模板类的参数中除了可以有类型参数,还可以有普通参数.例如: template<typename T, int N> class Demo{ }; N 是一个普 ...

  3. Immutable 详解及 React 中实践

    本文转自:https://github.com/camsong/blog/issues/3 Shared mutable state is the root of all evil(共享的可变状态是万 ...

  4. React 深入系列1:React 中的元素、组件、实例和节点

    文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助大家加深对React的理解,以及在项目中 ...

  5. React中ref的使用方法

    React中ref的使用方法 在react典型的数据流中,props传递是父子组件交互的唯一方式:通过传递一个新的props值来使子组件重新re-render,从而达到父子组件通信.当然,就像reac ...

  6. 深入理解react中的虚拟DOM、diff算法

    文章结构: React中的虚拟DOM是什么? 虚拟DOM的简单实现(diff算法) 虚拟DOM的内部工作原理 React中的虚拟DOM与Vue中的虚拟DOM比较 React中的虚拟DOM是什么?   ...

  7. react系列(五)在React中使用Redux

    上一篇展示了Redux的基本使用,可以看到Redux非常简单易用,不限于React,也可以在Angular.Vue等框架中使用,只要需要Redux的设计思想的地方,就可以使用它. 这篇主要讲解在Rea ...

  8. React中的高阶组件,无状态组件,PureComponent

    1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...

  9. Apache Beam中的函数式编程理念

    不多说,直接上干货! Apache Beam中的函数式编程理念 Apache Beam的编程范式借鉴了函数式编程的概念,从工程和实现角度向命令式妥协. 编程的领域里有三大流派:函数式.命令式.逻辑式. ...

随机推荐

  1. kubectl管理kubernetes集群

    [root@master ~]# kubectl get nodes  查看集群节点NAME      STATUS    AGEnode1     Ready     25mnode2     Re ...

  2. 创建docker静态化IP

    配置桥接网络 桥接本地物理网络的目的,是为了局域网内用户方便访问 docker 实例中服务,不需要各种端口映射即可访问服务. 但是这样做,又违背了 docker 容器的安全隔离的原则,工作中辩证的选择 ...

  3. HTML状态消息和方法

    参考链接1 参考链接2 当浏览器从 web 服务器请求服务时,可能会发生错误. HTML消息 1xx: 信息 消息: 描述: 100 Continue 服务器仅接收到部分请求,但是一旦服务器并没有拒绝 ...

  4. Bzoj3073Journeys

    这不裸的dij吗?来,弄他. 打完以后发现不妙,这数据范围略神奇……算一算,考一场都可能跑不出来.map去重边(成功额外引入log)不怕,交.TLE,54. 这不玩呢吗,把map去了,交.MLE,71 ...

  5. 统计网络time_wait连接状态及tcpip连接数

    netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' LAST_ACK 1 SYN_RECV 14 ESTABLIS ...

  6. koa 基础(十六)koa 中 session 的使用

    1.app.js /** * koa 中 session 的使用 * 1.npm install koa-session --save * 2.const session = require('koa ...

  7. 通过merge语句完成表数据同步

    此例中需要将center库中的tb_sys_sqlscripe表同步到branch,简单的语法如下:   merge into tb_sys_sqlscripe@branch b using tb_s ...

  8. 删除github上的一个repository

    在学github之初,我们免不了会建立一些简单的repository,那么,如何删掉它们呢? 第一步,打开所要删除的repository,比如:meilin.github.io 第二步,点击setti ...

  9. Redis Cluster 官方集群搭建指南

    安装ruby环境因为官方提供的创建集群的工具是用ruby写的,需要ruby2.2.2+版本支持,ruby安装需要指定openssl. 安装openssl $ wget https://www.open ...

  10. MySQL 给已存在的数据表 增加字段和注释

    MySQL 给已存在的数据表 增加字段和注释 问题描述 在开发一个系统的过程中,经常会遇到随着系统服务功能的扩展,或者服务之间的关联,需要适当的修改原有的表结构,比如,增加一些必要的字段. 示例:在已 ...