函数式编程简要概念

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

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

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


// 非纯函数
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. shiro 自定义filter

    1.自定义登录filter package com.creatunion.callcenter.filter; import com.alibaba.fastjson.JSONObject; impo ...

  2. TCP之RST报文段

    TCP 首部中的 RST 比特是用于 "复位" 的.一般来说,无论何时一个报文段发往基准的连接(referenced connection)出现错误,TCP 都会发出一个复位报文段 ...

  3. TCP之11种状态变迁

    1. TCP 之11种状态变迁 TCP 为一个连接定义了 11 种状态,并且 TCP 规则规定如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态.如,当某个应用进程在 CLOSED 状 ...

  4. python:科学计数法转化为浮点型数据

    def as_num(x): y='{:.5f}'.format(x) # 5f表示保留5位小数点的float型 return(y) 实验一下 as_num(1.2e-4) In [3]:as_num ...

  5. 自定义组合控件SettingItemView的简单实现

    package com.loaderman.settingitemviewdemo; import android.os.Bundle; import android.support.v7.app.A ...

  6. django使用session来保存用户登录状态

    先建好登录用的model,其次理解使用cookie和session的原理,一个在本机保存,一个在服务器保存 使用session好处,可以设置登录过期的时间, 编写views中login的函数 def ...

  7. CreateCompatibleBitmap 的使用

    函数功能:该函数创建与指定的设备环境相关的设备兼容的位图. 函数原型:HBITMAP CreateCompatibleBitmap(HDC hdc,int nWidth,int nHeight): 参 ...

  8. 支付宝API

    1. 业务场景描述:在我们的爱旅行项目中,用户只有报名并且付款之后才能算作参加旅行团成功,因此必须提供快速便捷的支付功能:这里,我们选择了阿里的支付宝api来实现快捷支付,基于此,我们向蚂蚁金服发起申 ...

  9. 【GO】一个容易踩坑的内外变量屏蔽问题

    package main import ( "errors" "fmt" ) func et()(string,error){ return "&qu ...

  10. Sql Service 存储过程、触发器

    if exists (select * from sysobjects where name='tb_admin') drop table tb_admin go create table tb_ad ...