本文将经过如何使用React Hook的API来构建react项目,摒弃传统的redux,经过使用useReducer和useContext等来实现状态分发管理,在最后会讲述如何在React Hook项目进行异步的数据请求。 Hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。如何你对React Hook的概念还不是特别清楚,请移步React官网的相关介绍查看这个新特性。javascript
了解redux的同窗应该知道react-redux中的Provier组件,经过Provider组件能够实现将写好的store进行状态分发到下级任意一个子组件中。其实去查看Provier的实现源码,能够发现正好是使用了react的context属性,因此咱们在这里一样使用React Hook的useContext属性实现状态分发。html
const value = useContext(myContext)
复制代码
useContext接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。java
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。react
除此以外,还得配合上useReducer,ios
const [state, dispatch] = useReducer(reducer, initialArg, init);
复制代码
useReducer是useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(若是你熟悉 Redux 的话,就已经知道它如何工做了。)git
有了这两项API咱们就能够开始编写Provider组件了。首先建立context目录,建立好基本的reducer和index文件,mainReducer.js实现代码以下:github
import mainConstants from './mainConstants';
export const mainInitialState = {
error: '',
res: [],
url: '/pool/query',
loading: false,
};
export default (state, action) => {
switch (action.type) {
case mainConstants.INIT_PAGE:
return {...state, res: action.payload};
case mainConstants.TO_SEARCH:
return {...state, url: action.payload};
case mainConstants.PAGE_LOADING:
return {...state, loading: action.payload};
case mainConstants.CHANGE_ERROR:
return {...state, error: action.payload};
default:
return state;
}
};
复制代码
index.js实现代码以下:redux
import React, { useReducer, createContext } from 'react';
import mainReducer, {mainInitialState} from './main/mainReducer';
const context = createContext({});
const reducer = {
main: mainReducer
};
// 添加状态更改的log
function middlewareLog(lastState, action) {
const type = action.type.split('_')[0].toLowerCase();
const nextState = reducer[type](lastState, action);
console.log(
`%c|------- redux: ${action.type} -------|`,
`background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`,
);
console.log('|--last:', lastState);
console.log('|--next:', nextState);
return nextState;
}
const Provider = props => {
const [mainState, mainDispatch] = useReducer(middlewareLog, mainInitialState);
const combined = {
main: {
state: mainState,
dispatch: mainDispatch,
},
};
return (<context.Provider value={combined}> {props.children} </context.Provider>) }; export {context}; export default Provider; 复制代码
而后在项目的主入口,加入Provier组件,实现状态分发管理,axios
import React from 'react';
import ReactDOM from 'react-dom';
import Provider from './context/';
import App from './App';
ReactDOM.render(<Provider> <App /> </Provider>, document.getElementById('root'));
复制代码
状态分发已经建立完毕,接下来是看如何在组件中获取状态和使用状态。App.js代码以下:数组
import React, {useContext} from 'react';
import {context} from "./context";
function App() {
const {state, dispatch} = useContext(context).main;
return (
<div> <div> hello </div> </div>
);
}
export default App;
复制代码
接下来咱们设定一个业务场景,一、页面初始加载数据,二、能够根据请求参数进行从新加载数据。 根据redux的三大原则,建立action文件,代码以下:
import mainConstants from './mainConstants';
export const initPage = value => ({type: mainConstants.INIT_PAGE, payload: value});
export const toSearch = value => ({type: mainConstants.TO_SEARCH, payload: value});
export const pageLoading = value => ({type: mainConstants.PAGE_LOADING, payload: value});
export const changeError = value => ({type: mainConstants.CHANGE_ERROR, payload: value});
复制代码
mainConstants代码以下:
const create = str => 'MAIN_' + str;
export default {
INIT_PAGE: create('INIT_PAGE'),
TO_SEARCH: create('TO_SEARCH'),
PAGE_LOADING: create('PAGE_LOADING'),
CHANGE_ERROR: create('CHANGE_ERROR'),
}
复制代码
再想一下业务场景,想要在页面渲染的时候去获取数据如何作呢?根据搜索框提供的参数又如何来向接口传递呢?这边主要使用到了useEffect。
useEffect(didUpdate);
复制代码
useEffect可让你在函数组件中执行反作用,操做数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于反作用。
若是你熟悉 React class 的生命周期函数,你能够把 useEffect Hook 看作 componentDidMount,componentDidUpdate 和 componentWillUnmount这三个函数的组合。
它接受两个参数,第一个参数是一个执行函数,这个执行函数是怎么处理以及何时执行,须要看第二个参数,通常地若是想执行只运行一次的 effect(仅在组件挂载和卸载时执行),能够传递一个空数组([]),相似于生命周期中的componentDidMount。若是想要根据状态值去执行,那么只须要将状态值传入数组便可。这个API有不少规范须要探究,能够移步官方文档的FAQ进行查看。
接着改写App.js, 代码以下:
import React, {useContext, useEffect} from 'react';
import {context} from "./context";
import * as mainAction from "./context/main/mainAction";
import {getJson} from "./util";
function App() {
const {state, dispatch} = useContext(context).main;
// 设计内部变量ignore,而且在ignore为True时改变状态,
// 最后返回一个执行操做,目的在于组件卸载时,禁止修改状态
useEffect(() => {
let ignore = false;
const getData = async () => {
try {
dispatch(mainAction.pageLoading(true));
const res = await getJson(state.url);
if (!ignore) {
dispatch(mainAction.initPage(res));
}
dispatch(mainAction.pageLoading(false));
} catch (err) {
if (!ignore) {
dispatch(mainAction.changeError(err.message));
}
}
};
getData();
return () => {
ignore = true
};
}, [state.url, dispatch]); // 只在url更改的时候执行
return (
<div> <div> <button onClick={() => dispatch(mainAction.toSearch(state.url + '?bagName=224-truck2_2019-05-09-14-28-19_41-0'))}>search</button> {state.res.map(item => <p key={item.id}>{item.bagName}</p> )} </div> </div>
);
}
export default App;
复制代码
这边是封装了一个axios的请求函数,代码以下:
import axios from 'axios';
const instance = getDefaultInstance();
export function getJson(url, data) {
return instance.get(url, { params: data });
}
function getDefaultInstance() {
const instance = axios.create({
baseURL: '/',
withCredentials: true
});
instance.interceptors.response.use(res => {
return res.data.data;
}, err => {
throw err;
});
return instance;
}
复制代码
官方文档中说明,但愿咱们将异步请求的函数直接放在useEffect中,而不是在组件内。
以上就实现了最初设定的业务场景,不过还有一个能够优化的地方。像平时工做中,几乎每一个页面都会有初始请求数据和查询数据的功能,因此咱们能够自定义Hook,将相同逻辑的部分实现封装。
当咱们想在两个函数之间共享逻辑时,咱们会把它提取到第三个函数中。而组件和 Hook 都是函数,因此也一样适用这种方式。
自定义 Hook 是一个函数,其名称以 “use” 开头,并且官方规定,必需要以“use”开头,函数内部能够调用其余的 Hook
想法是自定义的hook本身管理state,因此这里用到了useState,
const [state, setState] = useState(initialState);
复制代码
useState是返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次从新渲染加入队列。
在后续的从新渲染中,useState 返回的第一个值将始终是更新后最新的 state。
改写App.js,代码以下:
import React, {useContext, useEffect, useState} from 'react';
import {context} from "./context";
import * as mainAction from "./context/main/mainAction";
import {getJson} from "./util";
function useInitPage({state, dispatch, action}) {
const [res, setRes] = useState(state.res);
const [url, setUrl] = useState(state.url);
const addValue = url => setUrl(url);
useEffect(() => {
let ignore = false;
const getData = async () => {
try {
dispatch(action.pageLoading(true));
const res = await getJson(url);
if (!ignore) {
setRes(res); // 也能够不返回res
dispatch(action.initPage(res));
}
dispatch(action.pageLoading(false));
} catch (err) {
if (!ignore) {
dispatch(action.changeError(err.message));
}
}
};
getData();
return () => { ignore = true };
}, [url, dispatch]);
return {res, addValue};
}
function App() {
const {state, dispatch} = useContext(context).main;
const {res, addValue} = useInitPage({state, dispatch, action: mainAction});
useEffect(() => {
if (state.error !== '') {
alert(state.error);
}
}, [state.error]);
return (
<div> <div> <button onClick={() => addValue(state.url + '?bagName=224-truck2_2019-05-09-14-28-19_41-0')}>search</button> {res.map(item => <p key={item.id}>{item.bagName}</p> )} </div> </div>
);
}
export default App;
复制代码
本文主要经过React Hooks的几个新的API来实现了业务场景中的基础部分,实现代码略简单,概念介绍的也简单了些,不过从上手来看,仍是很是实用的。class组件存在某些问题使得上手难度略高,这也是官方推荐使用React Hook的缘由。官方最后公布并无计划移除class,不过仍是推荐class和Hook并行的策略。