这一次彻底搞定 useReducer - useContext使用

useReducer-基础概念篇

useReducer-使用篇

useReducer-配合useContext使用

欢迎回到我们的useReducer系列第三篇,如果这是你第一次看到这个系列,推荐先看看前两篇:

上篇文章结尾提到过使用useReducer,可以帮助我们集中式的处理复杂的state管理。但如果我们的页面很复杂,拆分成了多层多个组件,我们如何在子组件触发这些state变化呢,比如在LoginButton触发登录失败操作?

这篇文章会介绍如何使用另外一个高阶Hook-useContext去解决这些问题。

useContext从名字上就可以看出,它是以Hook的方式使用React Context。先简单介绍Context的概念和使用方式,更多Context的知识可以参考官方文档

context 介绍

下面这段定义来自官方文档:

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language. 
复制代码

简单来说Context的作用就是对它所包含的组件树提供全局共享数据的一种技术,talk is cheep 我们直接看官方Demo:

// 第一步:创建需要共享的context
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 第二步:使用 Provider 提供 ThemeContext 的值,Provider所包含的子树都可以直接访问ThemeContext的值
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}
// Toolbar 组件并不需要透传 ThemeContext
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton(props) {
  // 第三步:使用共享 Context
  const theme = useContext('ThemeContext');
  render() {
    return <Button theme={theme} />;
  }
}
复制代码

关于Context还有一个比较重要的点是:当Context Provider的value发生变化是,他的所有子级消费者都会rerender。

useContext版login

看完上面Demo,我们在回过头思考如何利用context去解决我们问中开头提到的子孙类组件出发reducer状态变化。没错,就是将dispatch函数作为context的value,共享给页面的子组件。

    // 定义初始化值
    const initState = {
        name: '',
        pwd: '',
        isLoading: false,
        error: '',
        isLoggedIn: false,
    }
    // 定义state[业务]处理逻辑 reducer函数
    function loginReducer(state, action) {
        switch(action.type) {
            case 'login':
                return {
                    ...state,
                    isLoading: true,
                    error: '',
                }
            case 'success':
                return {
                    ...state,
                    isLoggedIn: true,
                    isLoading: false,
                }
            case 'error':
                return {
                    ...state,
                    error: action.payload.error,
                    name: '',
                    pwd: '',
                    isLoading: false,
                }
            default: 
                return state;
        }
    }
    // 定义 context函数
    const LoginContext = React.createContext();
    function LoginPage() {
        const [state, dispatch] = useReducer(loginReducer, initState);
        const { name, pwd, isLoading, error, isLoggedIn } = state;
        const login = (event) => {
            event.preventDefault();
            dispatch({ type: 'login' });
            login({ name, pwd })
                .then(() => {
                    dispatch({ type: 'success' });
                })
                .catch((error) => {
                    dispatch({
                        type: 'error'
                        payload: { error: error.message }
                    });
                });
        }
        // 利用 context 共享dispatch
        return ( 
            <LoginContext.Provider value={dispatch}>
                <...>
                <LoginButton />
            </LoginContext.Provider>
        )
    }
    function LoginButton() {
        // 子组件中直接通过context拿到dispatch,出发reducer操作state
        const dispatch = useContext(LoginContext);
        const click = () => {
            if (error) {
                // 子组件可以直接 dispatch action
                dispatch({
                    type: 'error'
                    payload: { error: error.message }
                });
            }
        }
    }
复制代码

可以看到在useReducer结合useContext,通过context把dispatch函数提供给组件树中的所有组件使用 ,而不用通过props添加回调函数的方式一层层传递。

使用Context相比回调函数的优势:

  1. 对比回调函数的自定义命名,Context的Api更加明确,我们可以更清晰的知道哪些组件使用了dispatch、应用中的数据流动和变化。这也是React一直以来单向数据流的优势。

  2. 更好的性能:如果使用回调函数作为参数传递的话,因为每次render函数都会变化,也会导致子组件rerender。当然我们可以使用useCallback解决这个问题,但相比useCallbackReact官方更推荐使用useReducer,因为React会保证dispatch始终是不变的,不会引起consumer组件的rerender。

更多信息可以参考官方的FQA:

how-to-avoid-passing-callbacks-down

how-to-read-an-often-changing-value-from-usecallback

总结

至此useReducer系列三篇就全部结束了,我们简单回顾一下:

  • 如果你的页面state很简单,可以直接使用useState
  • 如果你的页面state比较复杂(state是一个对象或者state非常多散落在各处)请使用userReducer
  • 如果你的页面组件层级比较深,并且需要子组件触发state的变化,可以考虑useReducer + useContext

最后惯例,欢迎大家star我们的人人贷大前端团队博客,所有的文章还会同步更新到知乎专栏掘金账号,我们每周都会分享几篇高质量的大前端技术文章。如果你喜欢这篇文章,希望能动动小手给个赞。

参考资料

转载于:https://juejin.im/post/5d072b5e518825490d53dee6

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
<span style="font-size:24px;">前言</span><br /> <br /> <span style="font-size:18px;">如释重负,好用的技术就应该越来越简单</span><br /> React Hooks 是 React 16.8 从提案转为正式加入的新特性。这个新特性是个非常棒的设计。 可以说对于React 技术栈的发展具分割线一样的意义。讲师在课程中提到:之前使用 React 作为主要的前端技术,开发一款网页游戏。在整个游戏的各个模块中,Redux ,mobx,以及蚂蚁金服的 ant-design,dva, umi 这些框架或者第三方库都有涉及使用。但是自从了解了Facebook官方提案的 Hooks 特性后,才真正觉得获得了前所未有的解脱。如果你有React开发经验,学习了解 Hooks 后,一定有一种如释重负的轻松感。<br /> <br /> <span style="font-size:18px;">React 带来了方便也带来了迷茫</span><br /> 相信关心 React Hooks 这项新特性的童鞋,很多已经有了一定的 React 开发经验。那么你一定有所体验,React 给我们带来方便的同时,也的确和长久以来的前端开发模式有极大的不同。React 并不需要用继承,而是推荐用嵌套。React 有独特的 jsx 语法。大多数情况 jsx 都使得我们的代码更加简洁了。然而有些时候也给我们带来了一些困扰。 比如数据的传递,逻辑的复用。 react 是一种 mvvm 的设计模式,作为开发者一定要清楚,那些数据是业务数据,那些数据是UI数据。否则你的代码很有可能会陷入混乱局面。<br /> <br /> <span style="font-size:18px;">大型项目中模块化与功能解耦困难</span><br /> 在公司项目中 App 稍大的时候,我们发现状态提升和只通过 props 进行数据传递。很多时候都很难实现我们的需求。这时无论我们是否清楚的了解,但是状态管理也就是 redux mobx 等,轻易地进入到了公司的项目中。我们经过初期的尝试发现状态管理,确实比用纯粹的 React 带来了数据传递上的方便,以及代码组织上的清晰。但前提是你看懂且理解了 redux 大神晦涩的官网文档。 本来 React 被设计用来组件化前端开发。但当我们初期使用状态管理,我们常常会过度的使用状态数据,业务逻辑和ui逻辑没有清楚的分离,最终你的应用代码结果可能是:除了少数几个组件是独立的解耦的,大多数组件都因为状态数据的共享而耦合在了一起,且他们也完全依赖状态管理框架。无法再轻松的转移复用。使用高阶组件,属性渲染,渲染回调等高级特性,确实可以帮我们解决模块或功能的解耦问题。但是这些方法,确实有点超出普通“猿类”的技能。且降低了代码的可读性,对于团队协作,这是很致命的问题。<br /> <br /> <span style="font-size:18px;">React Hooks 真正开启前端模块化的金钥匙</span><br /> 对于以上问题,React Hooks 都有很好的解决方案,官方的设计动机就是解决这些曾经的繁琐,化繁为简。React Hooks 让我们在纯函数中就可以使用 React 的众多特性。而不必使用类。代码扁平,易读。解耦状态相关逻辑,UI逻辑和业务逻辑更好的分离。这些逻辑往往是纯函数,而以前很容易混合在类组件中。通过自定义 Hooks 我们可以把应用中“状态相关”逻辑解耦出来,独立编写到我们自己的hooks 中。从而更加易于复用和独立测试。<br />
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页