抛弃Redux,迎接React的hooks和context(一)
如果你使用React很长时间,Redux应该听说过。Redux是非常酷的,它是一种获取单独组件来改变和从主应用程序store中提取数据的方法,但是它不是非常容易入手的,尤其是新手。
有很多概念性的东西,比如reducers
, actions
, action creators
,并且有一些方法,比如mapDispatchToProps
和mapStateToProps
,以及需要根据常规原因创建的一堆文件和文件夹。为了分享和改编数据需要做大量的工作。
随着Context API和hooks的引入,我们可以在我们的React应用程序中重新创建Redux,而无需实际安装redux和react-redux,本篇文章将演示下如何操作。
配置
前提确保已经安装Nodejs(我使用的版本是v10.15.0),然后使用create-react-app
初始化一个app:
1 | npx create-react-app no-redux-app |
这个根据具体网络情况需要花一些时间,确实真的要好长时间
一旦初始化结束后,在no-redux-app这个目录下通过使用命令npm start
来启动项目,会自动打开浏览器,并且会看到类如下图的页面
ctrl + c
停止运行- 打开```package.json````文件,查看react和react-dom的版本号,如果是在16.7.0以上则可以使用hooks,如果不是请运行下面命令安装最新版本
1 | $ npm i [email protected] [email protected] |
- 为了保证下面的操作清晰,我们把src下面除了App.js、index.js和index.css文件的其他文件
- 修改index.js文件
1 | import React from 'react'; |
- 将App.js重命名为App.jsx,并用下面的代码替换
1 | import React from 'react'; |
Redux概念
根据它的文档介绍,Redux能够概括为三个基本概念:stores,actions和reducers
- action只有一个事情就是触发
state
的改变,它通常返回一个带有type和payload的对象
1 | function actionFunc(dispatch) { |
这里的dispatch参数告诉操作该对象需要影响的store是什么,因为应用程序可以有多个reducers。这将在以后有意义。
- reducer指定store将受操作影响的部分。因为redux存储是不可变的,所以reducer返回一个替换当前store的新store。Reducers通常写为switch语句。
1 | function visibilityFilter(state, action) { |
- store将所有应用程序数据保存在对象树中。Redux有一个store,但Facebook的Flux等其他state管理器可以拥有多个store。
1 | { |
- 应用程序中任何组件的组件都可以访问store,并可以通过触发action来更改store。
下面用React自带的hooks和context实现下这个概念
创建Store
- 在src目录下面创建一个Store.js文件
在这里,我们将使用react context来创建一个父组件,该组件将为其子组件访问它所拥有的数据。这里暂时不深入研究context,但基本上是有provider - consumer关系。provider拥有所有数据,consumer使用它(有意义)。
- 添加下面的代码到Store.js文件中
1 | import React from "react"; |
第3行创建了一个子组件将订阅的context对象。暂时跳过初始化state对象和reducer函数,先看下StoreProvider
- 添加下面的代码到StoreProvider中
1 | export function StoreProvider(props) { |
- 现在修改index.js文件并且从./Store导入StoreProvider
1 | import { StoreProvider } from './Store'; |
- 将组件放入到中,添加完的代码看起来类似如下
1 | ReactDOM.render( |
- 在App.jsx中导入Store,代码如下
1 | import { Store } from './Store'; |
- 在App函数中加入如下代码
1 | const store = React.useContext(Store); |
这里使用了第一个hooks,就是useContext。这将使组件可以访问context提供程序的value属性中的数据。
- 在
<React.Fragment>
里面第一行添加{console.log(store)}
- 启动程序打开浏览器,在开发工具控制台中将会看到```data from store````
如果你没有看到,请确认下你的代码是否跟我的一致
- 文件结构
- 代码结构
1 | // index.js |
// Store.js
1 | import React from 'react'; |
// App.jsx
1 | import React from 'react'; |
创建Reducer
如果看到这里的话,说明前面的实现是你已动手实现了下,并且是没有问题的。下面继续更新代码
- Store.js文件中,在initialState中加入下面的代码
1 | const initialState = { |
这是我们的初始store在添加任何新数据之前的样子。
- 修改reducer函数看起来像这样
1 | function reducer (state, action) { |
前面看到的reducer函数有两个参数,state - 运行时store中的数据,以及action - 返回的action对象。目前,我们的reducer有一个case ‘FETCH_DATA’,它将用传回的数据替换我们的films数组。如果调用了无效操作,则需要使用default关键字返回状态。
- 在StoreProvider函数中,添加下面的代码
1 | const [state, dispatch] = React.useReducer(reducer, initialState); |
这里遇到了第二个hook,就是useReducer。它需要两个参数reducer和initialState。它返回一个数组,里面分别是state - store里面的数据和dispatch - 我们如何向reducer发送动作(反过来改变我们的state)。我希望这是有意义的。
然后,我们将新状态转换为对象,并将其分配给名为value的变量。基本上价值是相同的
1 | const value = { |
但是可以在Javascript ES6及更高版本中编写更短的内容。
- 在Store.Provider中用下面的代码替换
value='data from store'
1 | value={value} |
现在我们可以将state和dispatch传递给子组件。
- 修改App.jsx文件,将
const store = React.useContext(Store)
替换为const { state, dispatch } = React.useContext(Store);
现在更新控制台日志中的store以查看状态并查看控制台。
你应该看到它从Store.jsx中提取我们的initialState数据。现在让我们来处理一些数据。
创建Action
我们的redux概念的最后一块。
- 修改App.jsx文件,再返回组件之前添加一个匿名函数,叫fetchDataAction
1 | const fetchDataAction = async () => {} |
我们将使用fetch api使用async/await从tvmazeapi获取数据。
- 添加新代码到我们的新fetchDataAction函数中
1 | const fetchDataAction = async () => { |
我建议您在浏览器中访问api url并查看数据。episodes列表在_embedded之下。
我们返回dispatch方法,其type和payload的对象作为属性,以便我们的reducer将知道要执行的是什么情况。
我们希望每次页面加载时都运行fetchDataAction,所以让我们将它放在返回组件的上方的useEffect hook中。
1 | React.useEffect(() => { |
上面的代码类似于componentDidMount。基本上应用程序加载,如果state.episodes为空(默认情况下),则运行fetchDataAction。
保存并刷新页面。查看开发工具控制台,您应该看到一些数据。
简而言之,这就是redux模式。某些情况触发了一个动作(在我们的例子中它是一个页面加载),动作在reducer中运行一个case,它反过来更新了store。现在让我们使用这些数据。
- 修改App.jsx,在
<p>Favourite</p>
下面加入如下代码
1 | <section> |
这段代码基本上遍历我们的剧集数组中的对象(在用api填充数据之后),并用这些数据填充dom。随意添加或删除您选择的数据点。
参考自:https://medium.com/octopus-labs-london/replacing-redux-with-react-hooks-and-context-part-1-11b72ffdb533