内容简介:本文将通过如何使用React Hook的API来构建react项目,摒弃传统的redux,通过使用useReducer和useContext等来实现状态分发管理,在最后会讲述如何在React Hook项目进行异步的数据请求。 Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。如何你对React Hook的概念还不是特别清楚,请移步React官网的相关介绍查看这个新特性。了解redux的同学应该知道react-redux中的
本文将通过如何使用React Hook的API来构建react项目,摒弃传统的redux,通过使用useReducer和useContext等来实现状态分发管理,在最后会讲述如何在React Hook项目进行异步的数据请求。 Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。如何你对React Hook的概念还不是特别清楚,请移步React官网的相关介绍查看这个新特性。
Provier组件
了解redux的同学应该知道react-redux中的 Provier组件 ,通过Provider组件可以实现将写好的store进行状态分发到下级任意一个子组件中。其实去查看Provier的实现源码,可以发现正好是使用了react的context属性,所以我们在这里同样使用react-hooks的useContext属性实现状态分发。
useContext
const value = useContext(myContext) 复制代码
useContext接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。
除此之外,还得配合上 useReducer ,
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init); 复制代码
useReducer是useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
有了这两项API我们就可以开始编写Provider组件了。首先创建context目录,创建好基本的reducer和index文件,mainReducer.js实现代码如下:
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实现代码如下:
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组件,实现状态分发管理,
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; 复制代码
接下来我们设定一个业务场景,1、页面初始加载数据,2、可以根据请求参数进行重新加载数据。 根据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
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); 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 都是函数,所以也同样适用这种方式。
自定义 Hook 是一个函数,其名称以 “use” 开头,而且官方规定,必须要以“use”开头,函数内部可以调用其他的 Hook
想法是自定义的hook自己管理state,所以这里用到了useState,
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; 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- nginx实现常见场景
- 深入解析Redis中常见的应用场景
- Redis常见七种使用场景(PHP实战)
- MySQL 的死锁系列:常见加锁场景分析
- TCP重置报文段及RST常见场景分析
- HBase场景 | 对比MySQL,一文看透HBase的能力及使用场景
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。