24

抛弃Redux,迎接React的hooks和context(一)

 4 years ago
source link: https://www.tuicool.com/articles/yu6JFfU
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如果你使用React很长时间,Redux应该听说过。Redux是非常酷的,它是一种获取单独组件来改变和从主应用程序store中提取数据的方法,但是它不是非常容易入手的,尤其是新手。

有很多概念性的东西,比如 reducersactionsaction creators ,并且有一些方法,比如 mapDispatchToPropsmapStateToProps ,以及需要根据常规原因创建的一堆文件和文件夹。为了分享和改编数据需要做大量的工作。

随着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 来启动项目,会自动打开浏览器,并且会看到类如下图的页面

z6j2aey.png!web

ctrl + c
$ npm i [email protected] [email protected]
  • 为了保证下面的操作清晰,我们把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能够概括为三个基本概念: storesactionsreducers

zmARzyF.jpg!web

  1. action 只有一个事情就是触发 state 的改变,它通常返回一个带有 typepayload 的对象
function actionFunc(dispatch) {
  return dispatch({type: 'COMPLETE_TODO', payload: 1}) 
}

这里的dispatch参数告诉操作该对象需要影响的store是什么,因为应用程序可以有多个reducers。这将在以后有意义。

  1. 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;
  }
}
  1. store 将所有应用程序数据保存在对象树中。Redux有一个store,但 Facebook的Flux 等其他state管理器可以拥有多个store。
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    text: 'Consider using Redux',
    completed: true,
  ]
}
  1. 应用程序中任何组件的组件都可以访问 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````

QniyIn3.png!web

如果你没有看到,请确认下你的代码是否跟我的一致

  • 文件结构

bIFfQfn.png!web

  • 代码结构
// 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 。它需要两个参数 reducerinitialState 。它返回一个数组,里面分别是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以查看状态并查看控制台。

UZfMzaE.png!web

你应该看到它从Store.jsx中提取我们的initialState数据。现在让我们来处理一些数据。

创建Action

我们的redux概念的最后一块。

  • 修改App.jsx文件,再返回组件之前添加一个匿名函数,叫fetchDataAction
const fetchDataAction = async () => {}

我们将使用 fetch api 使用async/await从 tvmaze api获取数据。

  • 添加新代码到我们的新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.episodes.length === 0 && fetchDataAction();
});

上面的代码类似于componentDidMount。基本上应用程序加载,如果state.episodes为空(默认情况下),则运行fetchDataAction。

保存并刷新页面。查看开发工具控制台,您应该看到一些数据。

z2AvQnI.png!web

简而言之,这就是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。随意添加或删除您选择的数据点。

m6fQB3Q.gif

参考自: https://medium.com/octopus-labs-london/replacing-redux-with-react-hooks-and-context-part-1-11b72ffdb533


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK