抛弃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:
npx create-react-app no-redux-app
这个根据具体网络情况需要花一些时间,确实真的要好长时间
一旦初始化结束后,在no-redux-app这个目录下通过使用命令npm start
来启动项目,会自动打开浏览器,并且会看到类如下图的页面
ctrl + c
停止运行- 打开```package.json````文件,查看react和react-dom的版本号,如果是在16.7.0以上则可以使用hooks,如果不是请运行下面命令安装最新版本
$ npm i react@16.8.6 react-dom@16.8.6
- 为了保证下面的操作清晰,我们把src下面除了App.js、index.js和index.css文件的其他文件
- 修改index.js文件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App.jsx';
import { StoreProvider } from "./Store";
ReactDOM.render(
<StoreProvider>
< App / >
</StoreProvider>
, document.getElementById('root'));
- 将App.js重命名为App.jsx,并用下面的代码替换
import React from 'react';
function App() {
return (
<React.Fragment>
<div>
<h1>Example</h1>
<p>Favourite</p>
</div>
</React.Fragment>
);
}
export default App;
Redux概念
根据它的文档介绍,Redux能够概括为三个基本概念:stores,actions和reducers
- action只有一个事情就是触发
state
的改变,它通常返回一个带有type和payload的对象
function actionFunc(dispatch) {
return dispatch({type: 'COMPLETE_TODO', payload: 1})
}
这里的dispatch参数告诉操作该对象需要影响的store是什么,因为应用程序可以有多个reducers。这将在以后有意义。
- reducer指定store将受操作影响的部分。因为redux存储是不可变的,所以reducer返回一个替换当前store的新store。Reducers通常写为switch语句。
function visibilityFilter(state, action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.payload;
default:
return state;
}
}
- store将所有应用程序数据保存在对象树中。Redux有一个store,但Facebook的Flux等其他state管理器可以拥有多个store。
{
visibilityFilter: 'SHOW_ALL',
todos: [
text: 'Consider using Redux',
completed: true,
]
}
- 应用程序中任何组件的组件都可以访问store,并可以通过触发action来更改store。
下面用React自带的hooks和context实现下这个概念
创建Store
- 在src目录下面创建一个Store.js文件
在这里,我们将使用react context来创建一个父组件,该组件将为其子组件访问它所拥有的数据。这里暂时不深入研究context,但基本上是有provider - consumer关系。provider拥有所有数据,consumer使用它(有意义)。
- 添加下面的代码到Store.js文件中
import React from "react";
export const Store = React.createContext();
const initialState = {};
function reducer () {}
export function StoreProvider(props) {}
第3行创建了一个子组件将订阅的context对象。暂时跳过初始化state对象和reducer函数,先看下StoreProvider
- 添加下面的代码到StoreProvider中
export function StoreProvider(props) {
return <Store.Provider value='data from store'>{props.children}</Store.Provider>
}
- 现在修改index.js文件并且从./Store导入StoreProvider
import { StoreProvider } from './Store';
- 将组件放入到中,添加完的代码看起来类似如下
ReactDOM.render(
<StoreProvider>
<App />
</StoreProvider>,
document.getElementById('root')
);
- 在App.jsx中导入Store,代码如下
import { Store } from './Store';
- 在App函数中加入如下代码
const store = React.useContext(Store);
这里使用了第一个hooks,就是useContext。这将使组件可以访问context提供程序的value属性中的数据。
- 在
<React.Fragment>
里面第一行添加{console.log(store)}
- 启动程序打开浏览器,在开发工具控制台中将会看到```data from store````
如果你没有看到,请确认下你的代码是否跟我的一致
- 文件结构
- 代码结构
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StoreProvider } from './Store';
ReactDOM.render(
<StoreProvider>
<App />
</StoreProvider>,
document.getElementById('root')
);
// Store.js
import React from 'react';
export const Store = React.createContext();
const initialState = {};
function reducer() {}
export function StoreProvider(props) {
return <Store.Provider value='data from store'>{props.children}</Store.Provider>;
}
// App.jsx
import React from 'react';
import { Store } from './Store';
export default function App() {
const store = React.useContext(Store);
return (
<React.Fragment>
{console.log(store)}
<div>
<h1>Example</h1>
<p>Favourite</p>
</div>
</React.Fragment>
);
}
创建Reducer
如果看到这里的话,说明前面的实现是你已动手实现了下,并且是没有问题的。下面继续更新代码
- Store.js文件中,在initialState中加入下面的代码
const initialState = {
films: [],
favourites: [],
};
这是我们的初始store在添加任何新数据之前的样子。
- 修改reducer函数看起来像这样
function reducer (state, action) {
switch(action.type) {
case 'FETCH_DATA':
return { ...state, films: action.payload };
default:
return state;
}
}
前面看到的reducer函数有两个参数,state - 运行时store中的数据,以及action - 返回的action对象。目前,我们的reducer有一个case 'FETCH_DATA',它将用传回的数据替换我们的films数组。如果调用了无效操作,则需要使用default关键字返回状态。
- 在StoreProvider函数中,添加下面的代码
const [state, dispatch] = React.useReducer(reducer, initialState);
const value = { state, dispatch };
这里遇到了第二个hook,就是useReducer。它需要两个参数reducer和initialState。它返回一个数组,里面分别是state - store里面的数据和dispatch - 我们如何向reducer发送动作(反过来改变我们的state)。我希望这是有意义的。
然后,我们将新状态转换为对象,并将其分配给名为value的变量。基本上价值是相同的
const value = {
state: state,
dispatch: dispatch
}
但是可以在Javascript ES6及更高版本中编写更短的内容。
- 在Store.Provider中用下面的代码替换
value='data from store'
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
const fetchDataAction = async () => {}
我们将使用fetch api使用async/await从tvmazeapi获取数据。
- 添加新代码到我们的新fetchDataAction函数中
const fetchDataAction = async () => {
const data = await fetch('https://api.tvmaze.com/singlesearch/shows?q=rick-&-morty&embed=episodes');
const dataJson = await data.json();
return dispatch({
type: 'FETCH_DATA',
payload: dataJson._embedded.episodes
});
}
我建议您在浏览器中访问api url并查看数据。episodes列表在_embedded之下。
我们返回dispatch方法,其type和payload的对象作为属性,以便我们的reducer将知道要执行的是什么情况。
我们希望每次页面加载时都运行fetchDataAction,所以让我们将它放在返回组件的上方的useEffect hook中。
React.useEffect(() => {
state.films.length === 0 && fetchDataAction();
});
上面的代码类似于componentDidMount。基本上应用程序加载,如果state.episodes为空(默认情况下),则运行fetchDataAction。
保存并刷新页面。查看开发工具控制台,您应该看到一些数据。
简而言之,这就是redux模式。某些情况触发了一个动作(在我们的例子中它是一个页面加载),动作在reducer中运行一个case,它反过来更新了store。现在让我们使用这些数据。
- 修改App.jsx,在
<p>Favourite</p>
下面加入如下代码
<section>
{
state.films.map(f => {
return (
<section key={f.id}>
<img
src={f.image ? f.image.medium : ''}
alt={`Year and Date ${f.name}`}
/>
<div>
{f.name}
</div>
<section>
<div>
Season: {f.season} Number: {f.number}
</div>
</section>
</section>
)
})
}
</section>
这段代码基本上遍历我们的剧集数组中的对象(在用api填充数据之后),并用这些数据填充dom。随意添加或删除您选择的数据点。
版权声明
由 durban创作并维护的 Gowhich博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证。
本文首发于 博客( https://www.gowhich.com ),版权所有,侵权必究。
本文永久链接: https://www.gowhich.com/blog/982
版权声明
由 durban创作并维护的 Gowhich博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证。
本文首发于 Gowhich博客( https://www.gowhich.com ),版权所有,侵权必究。
本文永久链接: https://www.gowhich.com/blog/982