超性感的React Hooks(九)useContext实践

简介: 这是一个需要在实践中,不断去总结,优化才能获得的技能。首先,将一个复杂的页面逻辑进行拆分的目的,一定是为了可读性和可维护性。如果你的组件拆分违背了这两个原则,那么拆分就有问题。本来我想根据我自己的经验,将组件分为基础组件,工具组件,容器组件,页面组件等大类,但是强行引入这些概念并不利于学习,还是建议大家自己在实践过程中去总结适合自己的拆分思维。不过,有一些原则可以分享给大家

微信图片_20220510142939.gif


如上图所示。


我们利用useContext来实现这个小demo。在实现之前,复习一下相关比较重要的知识点。


如下图。


微信图片_20220510142935.jpg


1


如何合理的拆分组件?


这是一个需要在实践中,不断去总结,优化才能获得的技能。


首先,将一个复杂的页面逻辑进行拆分的目的,一定是为了可读性和可维护性。如果你的组件拆分违背了这两个原则,那么拆分就有问题。


本来我想根据我自己的经验,将组件分为基础组件,工具组件,容器组件,页面组件等大类,但是强行引入这些概念并不利于学习,还是建议大家自己在实践过程中去总结适合自己的拆分思维。


不过,有一些原则可以分享给大家


1.当一个组件行数过长,例如超过150行,就应该反思组件是否合理2.页面组件应该逻辑简单,一目了然3.复用程度高的功能/模块可以定义成一个组件4.避免过度拆分,对阅读带来困扰5.合理处理组件的状态,该状态仅在该组件使用,则无需定义在父级


组件的拆分,是考验我们React水平的重要标准,但这不是通过一篇两篇文章就能够马上掌握的技能,因此多给自己一点耐心,多从实践中反复思考总结是非常好的进步方式。


2


首图展示了我们想要实现的页面效果。建议大家先自己尝试实现,再参考我的实现思路进行对比。这样更有利于掌握知识。


首先我们肯定要先思考如何进行组件拆分。


一、通过观察我们发现,一定会有共享的数据,因此我们可以利用context自定义一个Provider的顶层父组件。


二、Tab切换,可以封装成为一个工具组件。但是由于我们的实现效果相对简单,这个地方也只使用一次,因此我选择直接实现。如果放在大型项目中,即使实现比较简单,也应该封装成为一个组件,以供其他页面复用。


三、共有三个Tab页,每一个Tab页都有各自的内容。因此这三个页面应该各自封装成独立的组件。


四、首页模块有一个轮播图功能,这个我们也应该考虑封装为一个工具组件

五、设置模块的步进器可以封装成为一个基础组件


这样梳理下,这个Demo的组件主要结构就应该如下:


微信图片_20220510142931.png


3


接下来一个非常重要的思考,就是哪些状态应该在什么组件下来维护。


这一点非常重要,很少有人能够把这个事情做正确。混乱的状态管理,导致了代码非常糟糕。本来很简单的逻辑,可维护起来非常痛苦。太多的人没有去思考这个问题。


在顶层组件Provider中,我们只关心会被不同组件共享的数据。经过分析发现,只有首页和热门的未读标记数字,需要放在Provider中来处理。页面组件App和设置组件Setting都会使用到它们。


其他组件的状态都仅仅只是当前组件自己使用,因此就在各自的组件里维护就行了。


理清了这些思路,实现起来将会非常简单。


4


创建顶层组件Provider,该组件仅仅只维护两个未读的数据。


import React, { createContext, useState, Dispatch, ReactNode } from 'react';
interface Injected {
  unreadIndex: number,
  setUnreadIndex: Dispatch<any>,
  unreadHot: number,
  setUnreadHot: Dispatch<any>,
}
export const ctx = createContext<Injected>({} as Injected);
interface Props {
  children?: ReactNode
}
export function Provider({children}: Props) {
  const [unreadIndex, setUnreadIndex] = useState(0);
  const [unreadHot, setUnreadHot] = useState(0);
  const value = {
    unreadIndex,
    setUnreadIndex,
    unreadHot,
    setUnreadHot,
  }
  return (
    <ctx.Provider value={value}>{children}</ctx.Provider>
  )
}


接下来创建页面组件App,该组件会引入三个Tab页组件,并实现tab切换功能。还需要显示未读的状态。实现如下:


import React, {useContext, useState} from 'react';
import {ctx, Provider} from './context';
import {Badge} from 'antd-mobile';
import Home from './components/Home';
import Hot from './components/Hot';
import Setting from './components/Setting';
import './index.scss';
function App() {
  const {unreadIndex, unreadHot} = useContext(ctx);
  const [tabIndex, setTabindex] = useState(0);
  return (
    <div className="use_context_container">
      <div className="tab_wrapper">
        <Badge text={unreadIndex} style={{ marginLeft: 42 }}>
          <div onClick={() => setTabindex(0)}>首页</div>
        </Badge>
        <Badge text={unreadHot} style={{ marginLeft: 42 }}>
          <div onClick={() => setTabindex(1)}>热门</div>
        </Badge>
        <div onClick={() => setTabindex(2)}>设置</div>
      </div>
      <div className="content_wrapper">
        {tabIndex === 0 && (
          <Home />
        )}
        {tabIndex === 1 && (
          <Hot />
        )}
        {tabIndex === 2 && (
          <Setting />
        )}
      </div>
    </div>
  )
}
export default () => (
  <Provider>
    <App />
  </Provider>
)


Home组件通过知乎日报的接口请求到数据,并展示出来。除此之外,还需要重置Home的未读数字。因此需要借助useContext来访问setUnreadIndex,将对应的状态重置。


import React, { useState, useEffect, useContext } from 'react';
import {zhLastFeedApi, Feed} from './api';
import { ActivityIndicator, Carousel } from 'antd-mobile';
import {ctx} from '../../context';
import './style.scss';
// 执行如下指令,禁用chrome跨域限制
// open -a "Google Chrome" --args --disable-web-security  --user-data-dir
export default function ZhihuFeed() {
  const [feed, setFeed] = useState<Feed>();
  const {setUnreadIndex} = useContext(ctx);
  useEffect(() => {
    setUnreadIndex(0);
    zhLastFeedApi().then(res => {
      setFeed(res);
    });
  }, []);
  if (!feed) {
    return <div className="feed_container loading"><ActivityIndicator /></div>
  }
  const {stories, top_stories} = feed;
  return (
    <div className="feed_container">
      <Carousel infinite autoplay dots={false}>
        {top_stories.map((item, i) => (
          <a className="top_feed_item" key={i} href={item.url} style={{backgroundImage: `url(${item.image})`}}>
            <div className="title">{item.title}</div>
          </a>
        ))}
      </Carousel>
      <div className="inner">
        {stories.map((item, i) => (
          <a className="feed_item" href={item.url} key={i}>
            <img src={item.images![0]} alt=""/>
            <div className="info">
              <div className="title">{item.title}</div>
              <div className="tip">{item.hint}</div>
            </div>
          </a>
        ))}
      </div>
    </div>
  )
}


Hot组件与Home组件逻辑几乎一样,访问博客园的接口并展示数据。


import React, { useState, useEffect, useContext } from 'react';
import {topViewApi} from './api';
import { ActivityIndicator } from 'antd-mobile';
import { ctx } from '../../context';
import './style.scss';
// 执行如下指令,禁用chrome跨域限制
// open -a "Google Chrome" --args --disable-web-security  --user-data-dir
export default function ZhihuFeed() {
  const [feed, setFeed] = useState<string>();
  const {setUnreadHot} = useContext(ctx);
  useEffect(() => {
    setUnreadHot(0);
    topViewApi().then(res => {
      setFeed(res);
    })
  }, []);
  if (!feed) {
    return <div className="feed_container loading"><ActivityIndicator /></div>
  }
  return (
    <div className="blog_container" dangerouslySetInnerHTML={{__html: feed}}>
    </div>
  )
}


Setting组件需要设置unread的数字,因此状态从Provider中来。


import React, { useContext } from 'react';
import { List, Stepper } from 'antd-mobile';
import {ctx} from '../../context';
import './index.scss';
export default function Setting() {
  const {unreadIndex, unreadHot, setUnreadIndex, setUnreadHot} = useContext(ctx);
  return (
    <div className="setting_container">
      <div className="title">基本设置</div>
      <List>
        <List.Item wrap extra={
          <Stepper
            showNumber
            min={0}
            value={unreadIndex}
            onChange={setUnreadIndex}
          />}
        >
          首页未读
        </List.Item>
        <List.Item extra={
          <Stepper
            showNumber
            min={0}
            value={unreadHot}
            onChange={setUnreadHot}
          />}
        >
          热门未读
        </List.Item>
      </List>
      <div className="tip">该设置项仅仅用于展示context功能,实践中几乎不会有这样的需求,不过需要使用相同处理方式的需求很多</div>
    </div>
  )
}

这样,一个看上去比较复杂的案例,就简单的实现了。


很少有人能够把思路理到如此清晰,这,也正是你超越他人的机会所在。

相关文章
|
21天前
|
前端开发 JavaScript API
探究 React Hooks:如何利用全新 API 优化组件逻辑复用与状态管理
本文深入探讨React Hooks的使用方法,通过全新API优化组件逻辑复用和状态管理,提升开发效率和代码可维护性。
|
24天前
|
前端开发
深入探索React Hooks:从useState到useEffect
深入探索React Hooks:从useState到useEffect
21 3
|
29天前
|
前端开发 JavaScript
深入探索React Hooks:从useState到useEffect
深入探索React Hooks:从useState到useEffect
|
1月前
|
前端开发 JavaScript
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
手敲Webpack 5:React + TypeScript项目脚手架搭建实践
|
1月前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
34 2
|
2月前
|
前端开发 开发者
React 提供的其他重要 Hooks
【10月更文挑战第20天】React 提供了一系列强大的 Hooks,除了 `useRef` 之外,还有许多其他重要的 Hooks,它们共同构成了函数式组件开发的基础。
37 6
|
18天前
|
前端开发 JavaScript
React Hooks 深入解析
React Hooks 深入解析
20 0
|
18天前
|
前端开发
React Hooks:从基础到进阶的深入理解
React Hooks:从基础到进阶的深入理解
25 0
|
21天前
|
缓存 前端开发 开发者
深入理解React Hooks,打造高效响应式UI
深入理解React Hooks,打造高效响应式UI
29 0
|
2月前
|
前端开发 JavaScript 开发者
React Hooks
10月更文挑战第13天
37 1