React 中的函数式思想
函数式编程简要概念
函数式编程中一个核心概念之一就是纯函数,如果一个函数满足一下几个条件,就可以认为这个函数是纯函数了:
- 它是一个函数(废话);
- 当给定相同的输入(函数的参数)的时候,总是有相同的输出(返回值);
- 没有副作用;
- 不依赖于函数外部状态。
当一个函数满足以上条件的时候,就可以认为这个函数是纯函数了。举个栗子:
// 非纯函数
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
严格来说并不是函数式编程语言,在 JavaScript
中 this
的指向又非常容易的被改变,所以依赖于 this
关键字的 data 是非常不稳定的。
好在以上两个问题再 React
的 v0.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 Component
和 Presentational Component
的职责不仅可以更容易的复用组件,而且也容易定位问题的所在。
参考文章:
来源:https://segmentfault.com/a/1190000017750929
React 中的函数式思想的更多相关文章
- React中的响应式设计思想和事件绑定
这两个点是react入门非常重要的两个点,以前我们是直接操作dom的形式去做,react的设计思想和以前直接操作dom是完全不同的,react是一个响应式的框架,他在做编程的时候,强调的是我们不要直接 ...
- C++学习35 模板中的函数式参数
C++对模板类的支持比较灵活,模板类的参数中除了可以有类型参数,还可以有普通参数.例如: template<typename T, int N> class Demo{ }; N 是一个普 ...
- Immutable 详解及 React 中实践
本文转自:https://github.com/camsong/blog/issues/3 Shared mutable state is the root of all evil(共享的可变状态是万 ...
- React 深入系列1:React 中的元素、组件、实例和节点
文:徐超,<React进阶之路>作者 授权发布,转载请注明作者及出处 React 深入系列,深入讲解了React中的重点概念.特性和模式等,旨在帮助大家加深对React的理解,以及在项目中 ...
- React中ref的使用方法
React中ref的使用方法 在react典型的数据流中,props传递是父子组件交互的唯一方式:通过传递一个新的props值来使子组件重新re-render,从而达到父子组件通信.当然,就像reac ...
- 深入理解react中的虚拟DOM、diff算法
文章结构: React中的虚拟DOM是什么? 虚拟DOM的简单实现(diff算法) 虚拟DOM的内部工作原理 React中的虚拟DOM与Vue中的虚拟DOM比较 React中的虚拟DOM是什么? ...
- react系列(五)在React中使用Redux
上一篇展示了Redux的基本使用,可以看到Redux非常简单易用,不限于React,也可以在Angular.Vue等框架中使用,只要需要Redux的设计思想的地方,就可以使用它. 这篇主要讲解在Rea ...
- React中的高阶组件,无状态组件,PureComponent
1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...
- Apache Beam中的函数式编程理念
不多说,直接上干货! Apache Beam中的函数式编程理念 Apache Beam的编程范式借鉴了函数式编程的概念,从工程和实现角度向命令式妥协. 编程的领域里有三大流派:函数式.命令式.逻辑式. ...
随机推荐
- MySQL_(Java)使用preparestatement解决SQL注入的问题
MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC创建用户名和密码校验查询方法 传送门 MySQL数据库中的数据,数据库名garysql,表名gar ...
- 【黑马JavaWeb】.1.2反射机制
文章目录 反射:框架设计的灵魂 获取Class类对象的方式 学习视频:https://www.bilibili.com/video/av47886776?p=10 本来一万行的代码,使用框架以后简化到 ...
- 学习 vue 需要了解的内容
总结 vue 的目录 1. vue 基础 指令 事件 动态的属性 组件 动画 2. vue 组件通信 1. 父传子 props 2. 子传父 ref 3. 插槽 4. 组件的生命周期 3. vue 的 ...
- shiro环境搭建及基本操作
一.pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www ...
- TP框架如何绑定参数。目的进行ajax验证
TP框架的自动绑定 对于某些操作的情况(例如模型的写入和更新方法),可以支持参数的自动绑定,例如: 首先需要开启DB_BIND_PARAM配置参数: 'DB_BIND_PARAM' => tru ...
- Cortex-M3 在C中上报入栈的寄存器和各fault状态寄存器
因为在标准C语音中是不能获取SP指针的.因而,如果想通过C代码来获取入栈的寄存器值,需要配合一小段汇编代码来获取当前的SP值,然后再把这个SP值以参数形式传送给C代码,最后以指针的形式把栈中的各寄存器 ...
- [spring]基于注解的spring配置
Spring是一个基于IOC和AOP的结构J2EE系统的框架 IOC 反转控制 是Spring的基础,Inversion Of Control 简单说就是创建对象由以前的程序员自己new 构造方法来调 ...
- SQLSERVER大批量数据快速导入Redis
目的 把单表近5千万的某单个字段导入到Redis,作为一个list存储. 方案一: 使用sqlcmd工具(sqlserver自带),直接生成命令在Redis-cli中执行. 方案一. 使用sqlcmd ...
- androidstudio导入新项目build tools不符合问题解决
问题描述:从网上或者其他地方拷贝来完整代码导入androidstudio的时候,gradle过程显示build tools不符合 问题分析:你安装的SDK版本可能与其他人不一样,那么build的工具也 ...
- docker上传私有仓库报错
docker 1.17 版本搭建私有仓库,上传镜像报错:server gave HTTP response to HTTPS client” 系统环境:centos7docker版本:1.17(注意版 ...