函数式编程简要概念

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

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

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


// 非纯函数
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. Java线程之Dump

    什么是线程dump Java Thread dump记录了线程在jvm中的执行信息,可以看成是线程活动的日志.Java线程转储文件有助于分析应用程序和死锁情况中的瓶颈. 如何获取线程转储文件 在这里, ...

  2. Java线程之Callable、Future

    简述 在多线程中有时候我们希望一个线程执行完毕后可以返回一些值,在java5中引入了java.util.concurrent.Callable接口,它类似于Runnable接口,但是Callable可 ...

  3. [CSP-S模拟测试]:数学课(找规律+数学)

    题目传送门(内部题145) 输入格式 从$math.in$读入数据. 第一行两个数,为$n,q$.接下来$q$行每行一个数$m$,询问大小为$m$的$A$一共有多少个. 输出格式 输出答案到$math ...

  4. linux安装软件时/usr/lib/python2.7/site-packages/urlgrabber/grabber.py文件异常

    linux安装软件时,经常出现以下异常信息 Traceback (most recent call last): File , in <module> main() File , in m ...

  5. centos7编译安装Python 3.6.8 后用pip3出现SSL未配置问题(import ssl失败)解决方法

    下载源码编译安装openssl https://www.openssl.org/source/openssl-1.0.2j.tar.gz ./config --prefix=/usr/local/op ...

  6. Android应用开发编译框架流程与IDE及Gradle概要

    1 背景 建议阅读本文之前先阅读<Android Studio入门到精通>和<Groovy脚本基础全攻略>及<Gradle脚本基础全攻略>三篇博客作为背景知识,这样 ...

  7. leetcode324 摆动排序II

      1. 首先考虑排序后交替插入 首尾交替插入,这种方法对于有重复数字的数组不可行: class Solution { public: void wiggleSort(vector<int> ...

  8. day01-JavaScript中"Uncaught TypeError: Cannot set property 'innerHTML' of null"错误

    转行学开发,代码100天.初写了最简的一段Js代码,即通过document中的innerHTML方法修改一个<p>标签的内容,报以下错误. -"Uncaught TypeErro ...

  9. 六十:Flask.Cookie之flask设置cookie的有效域名

    设置cookie有效域名cookie默认只能在主域名下使用,如果要在子域名下使用,则应该给set_cookie设置属性domain='.主域名',此时,此cookie在此主域名下的所有子域名均有效 f ...

  10. java源码-ReentrantLock源码分析-1

    ReentrantLock 继承于lock是比较常用的独占锁,接下来我们来分析一下ReentrantLock源码以及接口设计: Sync是ReentrantLock的内部静态抽象类继承Abstract ...