前端状态管理:从Redux到Relay/GraphQL
1. Redux在Home组件中的应用
在使用Redux构建应用时,
action creator
函数是重要的组成部分,组件通过调用这些函数将负载(payload)分发到Redux存储中,最终导致状态的改变。有些操作在分发到存储之前需要先获取状态。下面以Neckbeard News应用的Home组件为例,展示如何将
action creator
函数传递给Redux存储。
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { Map } from 'immutable';
const emptyMap = Map()
.set(true, (<li style={listItemStyle}>...</li>))
.set(false, null);
class Home extends Component {
static propTypes = {
articles: PropTypes.arrayOf(
PropTypes.object
).isRequired,
fetchingArticles: PropTypes.func.isRequired,
fetchArticles: PropTypes.func.isRequired,
toggleArticle: PropTypes.func.isRequired,
filter: PropTypes.string.isRequired,
}
static defaultProps = {
filter: '',
}
componentWillMount() {
this.props.fetchingArticles();
this.props.fetchArticles(this.props.filter);
}
onTitleClick = id => () =>
this.props.toggleArticle(id);
render() {
const { onTitleClick } = this;
const { articles } = this.props;
return (
<ul style={listStyle}>
{emptyMap.get(articles.length === 0)}
{articles.map(a => (
<li key={a.id} style={listItemStyle}>
<button
onClick={onTitleClick(a.id)}
style={titleStyle}
>
{a.title}
</button>
<p style={{ display: a.display }}>
<small>
<span>{a.summary} </span>
<Link to={`articles/${a.id}`}>More...</Link>
</small>
</p>
</li>
))}
</ul>
);
}
}
export default connect(
(state, ownProps) => Object.assign(
state.get('Home').toJS(),
ownProps
),
dispatch => ({
fetchingArticles: () => dispatch({
type: 'FETCHING_ARTICLES',
}),
fetchArticles: (filter) => {
const headers = new Headers();
headers.append('Accept', 'application/json');
fetch(`/api/articles/${filter}`, { headers })
.then(resp => resp.json())
.then(json => dispatch({
type: 'FETCH_ARTICLES',
payload: json,
}));
},
toggleArticle: payload =>
dispatch({
type: 'TOGGLE_ARTICLE',
payload,
}),
})
)(Home);
connect
函数用于将
Home
组件连接到Redux存储,它接受两个函数作为参数:
- 第一个函数将不可变的
state
对象映射为JavaScript对象,并将
ownProps
合并到Redux存储数据中。
- 第二个函数将
action creator
函数设置为组件的属性,通过
dispatch
函数将负载传递到存储。
下面是与
Home
组件相关的
reducer
函数:
import { fromJS } from 'immutable';
const typeMap = fromJS({
FETCHING_ARTICLES: state =>
state.update('articles', a => a.clear()),
FETCH_ARTICLES: (state, payload) =>
state.set(
'articles',
fromJS(payload)
.map(a => a.set('display', 'none'))
),
TOGGLE_ARTICLE: (state, id) =>
state.updateIn([
'articles',
state
.get('articles')
.findIndex(a => a.get('id') === id),
'display',
], display =>
display === 'none' ?
'block' : 'none'
),
});
export default (state, { type, payload }) =>
typeMap.get(type, s => s)(state, payload);
这个
reducer
函数使用
typeMap
根据不同的操作类型来改变状态,逻辑清晰,系统中所有可能的变化都很明确。
2. Redux在移动应用中的状态管理
在React Native移动应用中也可以使用Redux。不过,与Web应用不同的是,移动应用的状态结构可能会有所不同。例如,Neckbeard News移动应用的初始状态如下:
import { fromJS } from 'immutable';
export default fromJS({
Main: {
title: 'All',
component: 'articles',
},
Categories: {
items: [
{
title: 'All',
filter: '',
selected: true,
},
{
title: 'Local',
filter: 'local',
selected: false,
},
{
title: 'Global',
filter: 'global',
selected: false,
},
{
title: 'Tech',
filter: 'tech',
selected: false,
},
{
title: 'Sports',
filter: 'sports',
selected: false,
},
],
},
Articles: {
filter: '',
items: [],
},
Article: {
full: '',
},
});
虽然状态结构不同,但Redux的基本原理在移动应用和Web应用中是相同的,都是为了支持特定的组件和应用的独特实现方式。
3. Redux架构的可扩展性
Redux是实现大型React应用的一种很好的方式,它具有可预测性、声明性、单向数据流和无副作用等优点。但随着应用功能和复杂性的增加,应用中的组件会增多,导致实现速度变慢,难以把握整体情况。
4. Relay和GraphQL简介
Relay是另一种用于处理React应用状态的方法,它依赖于GraphQL语言来获取和修改资源。与Redux相比,Relay可以解决一些扩展性方面的限制,它将重点放在组件的数据需求上。
以下是Relay和GraphQL的一些基本术语:
| 术语 | 定义 |
| ---- | ---- |
| Relay | 一个管理应用数据获取和数据修改的库,提供高阶组件将数据注入应用组件 |
| GraphQL | 一种用于指定数据需求和数据修改的查询语言 |
| 数据依赖 | 表示某个React组件依赖特定数据的抽象概念 |
| 查询 | 数据依赖的一部分,用GraphQL语法表示,由封装的Relay机制执行 |
| 片段 | 较大GraphQL查询的一部分 |
| 容器 | 一个Relay React组件,将获取的数据传递给应用React组件 |
| 突变 | 一种特殊的GraphQL查询,用于改变远程资源的状态,Relay需要在完成后将此更改反映到前端 |
5. 声明性数据依赖
Relay使用
colocation
来描述声明性数据依赖,即数据依赖与使用数据的组件放在一起,这样可以清楚地看到组件需要的数据。例如,要显示用户的名字和姓氏,可以这样做:
const User = ({ first, last }) => (
<section>
<p>{first}</p>
<p>{last}</p>
</section>
);
const UserContainer = Relay.createContainer(User, {
fragments: {
user: () => Relay.QL`
fragment on User {
first,
last,
}
`,
},
});
6. 应用状态的突变
Relay突变是会导致系统副作用的操作,因为它们会改变UI关心的资源状态。而且,Relay突变会考虑到由于状态改变而对数据产生的副作用。例如,更改用户的名字可能会影响显示用户详细信息的屏幕,也可能影响显示多个用户的列表屏幕。
下面是一个更改用户年龄的突变示例:
class ChangeAgeMutation extends Relay.Mutation {
static fragments = {
user: () => Relay.QL`
fragment on User {
id,
}
`,
viewer: () => Relay.QL`
fragment on Viewer {
id,
}
`,
}
getMutation() {
return Relay.QL`mutation{changeAge}`;
}
getFatQuery() {
return Relay.QL`
fragment on ChangeAgePayload @relay(pattern: true) {
user {
age,
},
viewer {
users,
},
}
`;
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
user: this.props.user.id,
viewer: this.props.viewer.id,
},
}];
}
getVariables() {
return {
age: this.props.age,
id: this.props.todo.id,
};
}
}
这个突变包含以下几个部分:
-
fragments
:在突变发生之前,告诉突变组件使用了哪些数据。
-
getMutation()
:从服务器获取实际的突变操作,用于改变后端资源。
-
getFatQuery()
:让Relay确定执行此突变可能产生的副作用。
-
getConfigs()
:告诉Relay要执行的突变类型,以便进行相应的规划。
-
getVariables()
:将突变的参数发送到后端GraphQL服务器执行实际的突变操作。
7. GraphQL后端和微服务
Relay在浏览器端工作,需要将GraphQL查询发送到后端。可以使用Node.js和一些GraphQL库来实现GraphQL后端,创建一个描述所有数据类型、查询和突变的模式(schema)。
现代Web应用通常由微服务组成,GraphQL可以帮助解决后端由多个微服务组成时的可扩展性问题,通过模式来解析数据,为前端提供有意义的数据。
综上所述,Relay和GraphQL为React应用的状态管理提供了一种新的思路,虽然有一定的学习曲线,但在可扩展性方面有一定的优势。通过声明性数据依赖和显式的突变副作用处理,可以更高效地管理应用状态。同时,需要一个GraphQL后端来支持数据的获取和修改。
前端状态管理:从Redux到Relay/GraphQL
8. Relay与Redux的对比
Relay和Redux都是用于处理React应用状态的方法,但它们在很多方面存在差异,以下是两者的对比分析:
| 对比项 | Redux | Relay |
| ---- | ---- | ---- |
| 实现复杂度 | 随着应用规模扩大,代码量增多,实现复杂度增加,需要更多的
action creator
、
reducer
等 | 减少了数据获取代码的复杂度,通过声明数据依赖来获取数据 |
| 可扩展性 | 当应用功能和复杂度增加时,会出现更多组件,实现速度变慢,难以把握整体情况 | 可以解决一些扩展性方面的限制,将重点放在组件的数据需求上 |
| 数据获取方式 | 需要手动编写
action creator
函数来获取数据,分散在多个模块中 | 使用声明性数据依赖,数据依赖与使用数据的组件放在一起,清晰明了 |
| 副作用处理 | 主要关注单向数据流和可预测的状态转换,对副作用的处理相对间接 | 显式处理突变的副作用,能更好地应对状态改变对数据产生的影响 |
9. 完整流程分析
下面通过mermaid流程图展示从Redux到Relay/GraphQL在前端状态管理中的完整流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{选择状态管理方式}:::decision
B -->|Redux| C(定义action creator函数):::process
C --> D(编写reducer函数):::process
D --> E(使用connect函数连接组件和store):::process
E --> F(组件调用action creator函数改变状态):::process
B -->|Relay/GraphQL| G(定义声明性数据依赖):::process
G --> H(创建Relay容器组件):::process
H --> I(编写GraphQL查询和突变):::process
I --> J(处理突变的副作用):::process
F --> K([结束]):::startend
J --> K
10. 实际操作步骤总结
无论是使用Redux还是Relay/GraphQL,在实际开发中都有相应的操作步骤:
-
Redux操作步骤
:
1. 定义
action creator
函数,用于创建不同类型的
action
。
2. 编写
reducer
函数,根据
action
的类型来改变状态。
3. 使用
connect
函数将组件连接到Redux存储,传递
action creator
函数和状态。
4. 在组件中调用
action creator
函数来触发状态的改变。
-
Relay/GraphQL操作步骤
:
1. 定义声明性数据依赖,使用
colocation
将数据依赖与组件放在一起。
2. 创建Relay容器组件,通过
createContainer
函数指定组件的数据依赖。
3. 编写GraphQL查询和突变,使用
Relay.QL
语法。
4. 处理突变的副作用,通过
getConfigs
等函数告诉Relay如何处理。
5. 确保有一个GraphQL后端,创建描述所有数据类型、查询和突变的模式。
11. 总结与展望
在前端开发中,状态管理是一个至关重要的环节。Redux和Relay/GraphQL都为React应用的状态管理提供了有效的解决方案。Redux以其可预测性和单向数据流的特点,适用于大多数规模的应用,但在处理大规模复杂应用时可能会遇到一些挑战。而Relay/GraphQL通过声明性数据依赖和显式的突变副作用处理,在可扩展性方面具有一定优势,能够更好地应对现代Web应用的复杂性。
在实际项目中,开发者可以根据项目的规模、复杂度和具体需求来选择合适的状态管理方式。对于小型项目,Redux可能是一个简单直接的选择;而对于大型复杂项目,Relay/GraphQL可能更能发挥其优势。同时,随着前端技术的不断发展,状态管理的方法也会不断演进,开发者需要持续学习和探索,以找到最适合自己项目的解决方案。
超级会员免费看
26

被折叠的 条评论
为什么被折叠?



