如何在常见业务场景中使用React Hook

栏目: IOS · Android · 发布时间: 5年前

内容简介:本文将通过如何使用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;
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learn Python the Hard Way

Learn Python the Hard Way

Zed A. Shaw / Addison-Wesley Professional / 2013-10-11 / USD 39.99

Master Python and become a programmer-even if you never thought you could! This breakthrough book and CD can help practically anyone get started in programming. It's called "The Hard Way," but it's re......一起来看看 《Learn Python the Hard Way》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具