React?设计模式?

2023-11-27 17:23:40 浏览数 (2)

❝安静下来,做该做的事 ❞

大家好,我是「柒八九」。一个「专注于前端开发技术/RustAI应用知识分享」Coder

前言

我们在使用React进行页面开发时候,为了能够达到组件复用的效果,想必大家都会使用一些看上去是「奇技淫巧」的方式来组织页面。

但是,在某种或者某些技巧的加持下,让我们的开发体验有了一种水银泻地的感觉。但是呢,如果有人进一步问你,你这个方式用的是什么模式,熟悉设计模式的同学可能就会往常规的设计模式上靠拢。不能说不对,只能说不是很准确的表达设计初衷。

这里多说一嘴。设计模式分为三大类。

而我们常说的「高内聚,低耦合」就是下面的模式准则的缩写版。

而在前端应用开发中,我们一般能接触到下面九种。

其实,针对每个框架都有属于自己的内部设计模式。也可以说是一种实现模式,它们支持「低耦合高内聚」模块,从而帮助我们创建可维护可扩展和高效的应用。

所以,今天我们就来谈谈,在React中的设计模式。下面的例子中,或许你平时用到过,但是不知道他的设计初衷是啥;有的例子可能大家在平时开发中没接触过,但是通过下面的案例分析,希望能帮大家在以后的工作中用的上

好了,天不早了,干点正事哇。

我们能所学到的知识点

  1. 前置知识点
  2. 容器和展示模式
  3. 组件组合与 Hooks
  4. 状态管理库
  5. Provider 模式
  6. 使用HOC增强组件
  7. Compound Components
  8. 受控模式
  9. 使用 forwardRefs 管理自定义组件

1. 前置知识点

「前置知识点」,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。「如果大家对这些概念熟悉,可以直接忽略」 同时,由于阅读我文章的群体有很多,所以有些知识点可能「我视之若珍宝,尔视只如草芥,弃之如敝履」。以下知识点,请「酌情使用」。 ❞

免费的 JSON api

想必大家在平时做项目或者是研究一个新技术时,当涉及到异步接口时,总是有点力不从心。有时候,会用硬编码将指定的数据格式写在逻辑业务中,亦或者通过本地mock数据做处理。但是呢,这有一个弊端就是这些都是本地数据,达不到那种异步效果。(当然也有专门的mock服务,但是我们在做展示时,就有点大材小用了)。

所以,从网上给大家找了几个比较好用的免费JSON API。下面只给出链接,具体如何使用,就需要大家动动手指了。(如果有必要的话,后期给大家单独写一篇相关文章)

多说一句,前端Mock数据,我们可以从如下角度考虑。

  1. {JSON} Placeholder[1]
    • 提供了 GETPOSTPUTPATCHDELETE 几个请求方法。
    • 还提供分页查询、具体 id 查询等功能。
  2. Reqres[2]
    • 支持了GETPOSTPUTDELETE
    • 模拟我们真实的接口返回格式
  3. publicapis[3]
    • 可以获取有关水果图片、营养信息,甚至是随机水果事实的数据
  4. randomuser[4]
    • 用于生成用户信息
    • 同时支持 JSON (默认格式)/PrettyJSON/CSV/YAML/XML
    • 还会生成随机的头像
  5. boredapi[5]
    • 其中最让人欲罢不能的是它能根据配置,产生随机数值

最后,给大家提供一个免费 API 的网站,大家可以根据自己的个人兴趣获取。开源 API[6]

fetch 简单介绍

fetch 是一个用于发起网络请求的现代 API,它是基于 Promise 的,并提供了一种更简单和强大的方式来进行网络通信。fetch API 主要用于获取资源(例如数据、图片等)。

fetch 的基本用法

代码语言:javascript复制
fetch(url, options)
  .then(response => {
    // 处理响应
    return response.json(); // 或者 response.text(), response.blob(), 等等
  })
  .then(data => {
    // 处理返回的数据
  })
  .catch(error => {
    // 处理错误
  });

fetch 不会将网络错误视为 reject,所以网络错误需要通过 .catch() 处理。 ❞

  • url: 请求的 URL。
  • options: 一个可选的配置对象,用于定制请求。

请求配置选项 (options) 的常见属性

  1. method: 请求方法,例如 GETPOST 等。
  2. headers: 包含请求头的对象,可以设置自定义的 HTTP 头信息。
  3. body: 请求体,通常用于 POST 请求,包含发送给服务器的数据。
  4. mode: 请求的模式,例如 corsno-corssame-origin
  5. credentials: 是否在请求中包含凭证,例如 includesame-originomit
  6. cache: 控制请求的缓存模式,例如 defaultno-storereload
  7. redirect: 控制跟随重定向的行为,例如 followerrormanual
  8. referrer: 指定 referrer,例如 no-referrerclient
  9. integrity: 包含请求的子资源完整性值(Subresource Integrity)。
  10. signal: 一个 AbortSignal 对象,用于允许中止请求。

例如有如下的例子。

代码语言:javascript复制
fetch('https://api.example.com/data', {
    method: "GET",
    credentials: "include",
    mode: "cors",
    headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    },
    signal: controller.signal
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

  1. method: "GET": 这指定了请求的 HTTP 方法。在这种情况下,是一个 GET 请求,用于从指定的 URL 检索数据。
  2. credentials: "include": 这个选项表示浏览器应该包括与请求相关的任何 cookie。通常在向不同域发出请求时使用,确保发送任何相关的身份验证 cookie。
  3. mode: "cors": 这为请求设置了 CORS(跨域资源共享)模式。CORS 是浏览器实施的安全功能,用于限制网页从与提供网页的域不同的域发出请求。"cors" 模式允许跨域请求。
  4. headers: 这是一个包含你想在请求中包含的任何自定义标头的对象。在这种情况下,它包括两个标头:
    • 'Content-Type': 'application/json':指示请求中发送的内容是 JSON。
    • 'Access-Control-Allow-Origin': '*':通常由服务器设置的响应标头,用于指定允许访问资源的起源。然而,在请求中设置此标头似乎有点不寻常。通常,这是服务器设置的响应标头。
  5. signal: controller.signal: 这允许你将请求与 AbortController 信号相关联。AbortController 可用于在需要时中断请求。如果相关联的 AbortController 被中止(通过调用 controller.abort()),请求将被中断。

AbortController

AbortController 是一个用于控制 fetch 请求中止的 API。它提供了一种方法,可以在请求尚未完成时中止或取消网络请求。这对于处理用户取消操作或在组件卸载时取消未完成的请求非常有用。

使用步骤:

「创建 AbortController 对象」

代码语言:javascript复制
const controller = new AbortController();

「获取 AbortSignal 对象」

代码语言:javascript复制
const signal = controller.signal;

「将 AbortSignal 与请求关联」

fetch 请求的选项中使用 signal 属性:

代码语言:javascript复制
const response = await fetch('https://api.example.com/data', {
  method: 'GET',
  signal: signal,
});

「中止请求」

通过调用 abort 方法中止请求:

代码语言:javascript复制
controller.abort();
示例
代码语言:javascript复制
const controller = new AbortController();
const signal = controller.signal;

try {
  const response = await fetch('https://api.example.com/data', {
    method: 'GET',
    signal: signal,
  });

  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Request aborted');
  } else {
    console.error('Error:', error);
  }
}

// 中止请求
controller.abort();
适用场景
  • 「取消请求」:当用户执行了取消操作时,可以使用 AbortController 来取消尚未完成的请求。
  • 「组件卸载时的资源清理」:在 React 或其他前端框架中,可以在组件卸载时使用 AbortController 来中止未完成的请求,防止在组件销毁后仍然更新组件状态。
注意事项
  • 使用 AbortController 需要考虑浏览器兼容性,确保你的目标浏览器支持该 API。
  • 中止请求后,fetch 返回的 Promise 会被拒绝,并且 catch 块中的错误对象的 name 属性将为 'AbortError'
  • 一些服务器可能不支持请求中止,因此并不是所有的请求都能成功中止。
  • 中止请求后,任何正在进行的网络请求都将被中止,不再返回响应。

使用 AbortController 可以提高应用的性能和用户体验,特别是在处理大量或长时间运行的请求时。

如何用一个变量来表示多个值

假设,现在有一个操作,你需要执行很多步,才可以完成最后的结果。然后中间的步骤可能需要(2步以上)。想必大家的一贯思维就是用一个变量来记录操作执行到哪步,但是如果需求有了一个变更,我们不仅需要准确的记录执行到哪步,我们还需要在某一个时刻,需要将其中步骤进行筛选处理。(类似于挑选特定的某组步骤),如果是这样的话,只通过一个变量就无法知晓,那些操作被执行过,也没法按照新的需求进行挑选操作。

针对上面的新的需求,大家可能会扩展这个变量,让其变成一个对象,然后冗余一些变量来完成上述操作。

今天,我们分享一种另外的解决方案。 我们可以通过位运算来完成这些操作。(下面的代码也是简单示例,是可以扩展的)

代码语言:javascript复制
const STEPS = {
    STEP_1: 1 << 0,  // 2^0 = 1
    STEP_2: 1 << 1,  // 2^1 = 2
    STEP_3: 1 << 2   // 2^2 = 4
};

// 初始化变量
let currentStep = 0;

// 执行步骤
currentStep |= STEPS.STEP_1;  // 完成步骤1
console.log('当前步骤:', currentStep);

currentStep |= STEPS.STEP_2;  // 完成步骤2
console.log('当前步骤:', currentStep);

// 检查步骤
if (currentStep & STEPS.STEP_1) {
    console.log('步骤1已完成');
}

if (currentStep & STEPS.STEP_2) {
    console.log('步骤2已完成');
}

if (currentStep & STEPS.STEP_3) {
    console.log('步骤3已完成');  // 不会执行,因为步骤3未完成
}

❝下面我们都以JSONPlaceholder的 API 接口作为示例。 ❞

2. 容器和展示模式

容器和展示模式是一种旨在将展示逻辑业务逻辑React 代码中分离的模式,从而达到模块化的效果,并「遵循关注点分离原则」

这也是在Hook还没流行之前,我们口中常说的,「容器组件」「展示组件」。大家可能会想,这都是Hook之前的组件拼装理念,这都2023,马上都2024了。肯定过时了,非也。其实,它还是有很大的用处的。

React 应用程序中,通常会出现需要从后端/缓存中获取数据或计算逻辑并在 React 组件上表示计算结果的情况。在这些情况下,容器和展示模式非常适用,因为它可以将组件分类为两种:

  1. 容器组件,负责数据获取或计算。
  2. 展示组件,负责在用户界面上呈现获取的数据或计算的值。

下面展示了,如何使用 React 实现一个简单的 PostList 组件,它会从后端获取 posts 列表,并将其渲染到页面上。

容器组件

代码语言:javascript复制
import React, { useEffect, useState } from 'react';
import PostList from './PostList';

const PostsContainer: React.FC = () => {
    const [posts, setPosts] = useState<Post[]>([]);
    const [isLoading, setIsLoading] = useState<boolean>(false);
    const [error, setError] = useState<boolean>(false);

    const getPosts = async () => {
        setIsLoading(true);
        try {
            const response = await fetch("https://jsonplaceholder.typicode.com/posts");
            const data = await response.json();
            setIsLoading(false);
            if (!data) return;
            setPosts(data);
        } catch (err) {
            setError(true);
        } finally {
            setIsLoading(false);
        }
    };

    useEffect(() => {
        getPosts();
    }, []);

    return <PostList loading={isLoading} error={error} posts={posts} />;
};

export default PostsContainer;

展示组件

代码语言:javascript复制
// 该组件负责显示文章

import React from "react";
import { Post } from "./types";

interface PostListProps {
  loading: boolean;
  error: boolean;
  posts: Post[];
}

const PostList: React.FC<PostListProps> = ({ loading, error, posts }) => {
  if (loading && !error) return <div>Loading...</div>;
  if (!loading && error) return <div>出错了,无法加载文章信息</div>;
  if (!posts) return null;

  return (
    <ul>
      {posts.map((post) => (
        <li key={pots.id}>{post.title}</li>
      ))}
    </ul>
  );
};

export default PostList;

3. 组件组合与 Hooks

Hooks 是在 React 16.8 中首次推出的全新功能。从那时起,它们在开发 React 应用程序中发挥着至关重要的作用。Hooks 是基本函数,「赋予函数组件访问状态和生命周期方法的能力」(以前仅限于类组件)。另外,Hooks 可以专门设计以满足组件的需求,并具有额外的用途。

在前面的美丽的公主和它的 27 个 React 自定义 Hook中我们介绍了,利用 27 个自定义Hook来处理业务中可能遇到的逻辑封装。

我们现在可以将所有有状态逻辑隔离出来,并在组件中使用自定义 Hooks 进行组合或使用。因此,代码更加模块化和可测试,因为 Hooks 与组件的联系较松散,可以单独测试。

让我们来创建一个自定义 Hook,用于获取文章评论。

代码语言:javascript复制
export const useFetchComments = () => {

    const [comments, setComments] = useState<Comment[]>([]);
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(false);
    const controller = new AbortController();

    const getComments = async () => {
        setIsLoading(true);
        try {
            const response = await fetch(
                "https://jsonplaceholder.typicode.com/posts/1/comments",
                {
                    method: "GET",
                    credentials: "include",
                    mode: "cors",
                    headers: {
                        'Content-Type': 'application/json',
                        'Access-Control-Allow-Origin': '*'
                    },
                    signal: controller.signal
                }
            );
            const data = await response.json();
            setIsLoading(false);
            if (!data) return;
            setComments(data);
        } catch(err) {
            setError(true);
        } finally {
            setIsLoading(false);
        }
    };

    useEffect(() => {
        getComments();
        return () => {
            controller.abort();
        };
    }, []);

    return [
        comments,
        isLoading,
        error
    ];
};

创建自定义 Hook 后,我们将其导入到组件中并使用它:

代码语言:javascript复制
// 导入自定义 Hook 到组件中并获取评论信息
import React from "react";
import { useFetchComments } from "./useFetchComments";

const CommentsContainer: React.FC = () => {
  const [comments, isLoading, error] = useFetchComments();

  return <CommentList loading={isLoading} error={error} comments={comments} />;
};

export default CommentsContainer;

4. 状态管理库

在实践中,当涉及到实际「状态存储」时,有两种主要方法。

❝第一种是「由 React 自身维护」。这通常意味着利用 React提供的API,如useStateuseRefuseReducer,结合React上下文来传播一个共享值。

  • 「但是」,这种情况,在遇到「大量数据」的传递时候,性能优化是一个不小的挑战。

❝第二种方式是「将数据存储在React外部」,然后以「单例」的形式存储。并且通过「发布-订阅」的模式来使得React组件树中的某个节点能够及时准确的获取到最新的值。从而避免因为一个值的变更,使得整个组件树重新发生渲染。

  • 「然而」,因为它是内存中的一个「单一值」,你不能为「不同的子树」提供不同的数据状态。

关于为何选择状态管理库我们之前在React-全局状态管理的群魔乱舞中介绍过,这里就不在过多的解释了。

在组件中处理许多状态时,往往会导致许多未分组的状态,这可能会让处理变得繁重且具有挑战性。在这种情况下,使用 全局状态库 模式可能是一个很好的选择。我们可以使用它们将状态分类为某些操作,当执行这些操作时,可以改变分组的状态。

这种模式允许使用它的开发人员控制组件和/或钩子的状态管理,使他们能够在事件被发送时管理状态变化。

其实,如果作为演示效果来讲,ReduxReducer来进行案例分析,但是呢,用过Redux的朋友都知道,它的样板代码太多。所以,我们选择比较火的使用Redux Toolkit来说明效果。


使用 Redux Toolkit 中的 createSlice 函数创建一个 slice

代码语言:javascript复制
// authSlice.js
import { createSlice } from "@reduxjs/toolkit";

const authSlice = createSlice({
  name: "auth",
  initialState: {
    loggedIn: false,
    user: null,
    token: null,
  },
  reducers: {
    login: (state, action) => {
      state.loggedIn = true;
      state.user = action.payload.user;
      state.token = action.payload.token;
    },
    logout: (state) => {
      state.loggedIn = false;
      state.user = null;
      state.token = null;
    },
  },
});

export const { login, logout } = authSlice.actions;
export default authSlice.reducer;

构建全局数据库Redux Store

代码语言:javascript复制
// store.js
import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./authSlice";

const store = configureStore({
  reducer: {
    auth: authReducer,
  },
});

export default store;

使用Redux ProviderReduxStore和我们应用做融合

代码语言:javascript复制
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import YourComponent from "./YourComponent";

ReactDOM.render(
  <Provider store={store}>
    <YourComponent />
  </Provider>,
  document.getElementById("root")
);

随后,我们就可以在组件中进行对应操作的触发了。

代码语言:javascript复制
// LoginComponent.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { login, logout } from "./authSlice";

const LoginComponent = () => {
  const dispatch = useDispatch();
  const authState = useSelector((state) => state.auth);

  const handleLogin = () => {
    dispatch(
      login({
        user: { id: 1, name: "前端柒八九" },
        token: "abc123",
      })
    );
  };

  const handleLogout = () => {
    dispatch(logout());
  };

  return (
    <div>
      <p>{`是否登录: ${authState.loggedIn}`}</p>
      {authState.loggedIn ? (
        <button onClick={handleLogout}>退出</button>
      ) : (
        <button onClick={handleLogin}>登录</button>
      )}
    </div>
  );
};

export default LoginComponent;

在上面的代码中,组件分发了两个操作:

  1. 'login' 操作类型触发了一个状态变化,影响了三个状态值,分别是 loggedIn、user、token。
  2. 'logout' 操作简单地将状态重置为其初始值。

5. Provider 模式

Provider模式在数据管理方面非常有用,它利用Context API 通过组件树传递数据。这种模式是解决 React 开发中常见的「属性穿透」问题的林丹妙药。

要实现Provider模式,首先我们将创建一个Provider组件。ProviderContext对象提供给我们的高阶组件。我们可以使用 React 提供的 createContext 方法构建一个上下文对象

代码语言:javascript复制
export const ThemeContext = React.createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = React.useState("light");

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

创建Provider后,我们将包裹依赖于来自上下文的数据的组件。

为了从上下文 API 获取数据,我们调用 useContext 钩子,该钩子「接受一个上下文作为参数」(在这种情况下是 ThemeContext)。

代码语言:javascript复制
import { useContext } from "react";
import { ThemeProvider, ThemeContext } from "../context";

const HeaderSection = () => {
  <ThemeProvider>
    <TopNav />
  </ThemeProvider>;
};

const TopNav = () => {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <div style={{ backgroundColor: theme === "light" ? "#fff" : "#000" }}>
      ...
    </div>
  );
};

这种模式在一些不经常变更的全局属性并用在组件树中又处处使用。这种方式真是百试不爽。


6. 使用HOC增强组件

高阶组件接受一个组件作为参数,并返回一个注入了额外数据或功能的增强组件」。在 React 中使用 HOC 的可能性是因为 React 更偏向于组合而非继承。

HOC模式提供了一种增加或修改组件功能的机制,促进了组件的重用和代码共享。在某些方面能达到奇效。

HOC中,我们可以把我们想要提取的参数进行剥离,然后对其进行特殊化处理。

代码语言:javascript复制
import React from "react";

const higherOrderComponent = (Component) => {
  return class HOC extends React.Component {
    state = {
      name: "前端柒八九",
    };
    // 注意:这里我们可以把在HOC中的变量通过`props`传递给`Component`
    // 同时,这里我们还可以有选择性的将外层的props进行传递(这里我们偷懒了)
    render() {
      return <Component name={this.state.name} {...this.props} />;
    }
  };
};

// 在这里我们可以通过props获取HOC传递过来的参数也可以获取在组件被调用时传递的参数
const AvatarComponent = (props) => {
  return (
    <div className="flex items-center justify-between">
      <div className="rounded-full bg-red p-4">{props.name}</div>
      <div>
        <p>人物描述:{props.description}.</p>
      </div>
    </div>
  );
};

const SampleHOC = higherOrderComponent(AvatarComponent);

const App = () => {
  return (
    <div>
      <SampleHOC description="专注于前端开发技术/Rust及AI应用知识分享的Coder" />
    </div>
  );
};

export default App;

❝其实上面的示例还是过于保守了,其实我们还可以在HOC中进行接口查询等异步任务。 ❞

代码语言:javascript复制
const HocCalendar = (WrappedComponent, customProps: CustomProps) => {
    return function HocCalendar(props) {
      const [ajaxResult,setAjaxResult] = useState({});
       useEffect(() => {
         // 这里做异步查询,然后更新state
         const fakeResult = {name:"前端柒八九",age:"18"}; 
         setAjaxResult(fakeResult)
       },[])
       return (
         <>
           <WrappedComponent {...props} ajaxResult={ajaxResult}/>
         </>
       )
    }
}

还有更多的用法,这里就不在展开说明了。


7. Compound Components

针对于React开发,Antd想必大家都用过。

代码语言:javascript复制
<Form>
    <Form.Item >
      <Input />
    </Form.Item>
    <Form.Item >
      <Select >
        <Select.Option value="male">male</Option>
        <Select.Option value="female">female</Option>
        <Select.Option value="other">other</Option>
      </Select>
    </Form.Item>
</From>

像上面的写法From.Item/Select.Option大家肯定不会陌生。

其实这也算是一种模式 - 复合模式

复合模式是一种用于管理由子组件组成的父组件的 React 设计模式。

这种模式的原则是将父组件分解为较小的组件,然后使用 propscontext 或其他 React 数据管理技术来管理这些较小组件之间的交互。

当需要创建由较小组件组成的可重用、多功能组件时,这种模式非常有用。它使开发人员能够创建复杂的 UI 组件,这些组件可以轻松定制和扩展,同时保持清晰简单的代码结构。

下面先展示一种比较简单的方式。

代码语言:javascript复制
import React, { createContext, useContext, useState } from "react";

const ToggleContext = createContext();

function Toggle({ children }) {
  const [on, setOn] = useState(false);
  const toggle = () => setOn(!on);

  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  );
}

Toggle.On = function ToggleOn({ children }) {
  const { on } = useContext(ToggleContext);
  return on ? children : null;
};

Toggle.Off = function ToggleOff({ children }) {
  const { on } = useContext(ToggleContext);
  return on ? null : children;
};

Toggle.Button = function ToggleButton(props) {
  const { on, toggle } = useContext(ToggleContext);
  return <button onClick={toggle} {...props} />;
};

function App() {
  return (
    <Toggle>
      <Toggle.On>打开</Toggle.On>
      <Toggle.Off>关闭</Toggle.Off>
      <Toggle.Button>切换</Toggle.Button>
    </Toggle>
  );
}

在上面的代码中,Toggle组件包含了Toggle.OnToggle.OffToggle.Button子组件。这些子组件可以根据Toggle组件的状态进行渲染,使得使用者可以轻松地创建具有灵活功能的复杂组件。

上面的例子将ToggleToggle.Xx,强耦合了,其实我们可以使用下面的方式做一次修正。(偷悄悄的说一下,这也是AntD的实现方式)

代码语言:javascript复制
import InnerAction from './Action';
import Step1 from './Step1';
import Step2 from './Step2';


type InnerActionType = typeof InnerAction;
type CompoundedComponent = InnerActionType & {
    Step1: typeof Step1;
    Step2: typeof Step2;
};

const Action = InnerAction as CompoundedComponent;

Action.Step1 = Step1;
Action.Step2 = Step2;

export default Action;

通过这种方式,我们可以很方便的将页面逻辑和代码很好的糅合到一起。

更好的做到见名知意。

比方说,一个操作有很多步骤(大于2步) ,通过位运算,达到一个控制操作流程的步骤。

代码语言:javascript复制
const STEPS = {
    STEP_1: 1 << 0,  // 2^0 = 1
    STEP_2: 1 << 1,  // 2^1 = 2
    STEP_3: 1 << 2   // 2^2 = 4
};
const Action = () => {
  const [currentStep,setCurrentStep] = useState(0);
  
  useEffect(()=>{
    currentStep |= STEPS.STEP_1; 
  },[])
  
  return (
    <>
      {currentStep & STEPS.STEP_1 && <Action.Step1/>}
      {currentStep & STEPS.STEP_2 && <Action.Step2/>}
      ......
    </>
  )

}

更有甚者,我们又可以单独使用这个组件,只要是export default ,然后又可以在其他的地方给它起一个符合页面逻辑的名称。

这里也步多展开说明了,大家自行探索哇。


8. 受控模式

受控模式可用于处理输入字段。这种模式涉及使用事件处理程序在输入字段的值更改时更新组件状态,并将输入字段的当前值存储在组件状态中。

由于 React 控制组件的状态和行为,相对于不使用组件状态并直接通过 DOM(文档对象模型)控制它的未控制输入模式,这种模式使代码更可预测和可读。

代码语言:javascript复制
import React, { useState } from "react";

function ControlledInput() {
  const [inputValue, setInputValue] = useState("");

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return <input type="text" value={inputValue} onChange={handleChange} />;
}

9. 使用 forwardRefs 管理自定义组件

一个名为 forwardRef 的高阶组件接受另一个组件作为输入,并输出一个新组件,该新组件传递了原始组件的 ref。通过这样做,子组件的 ref对于父组件是可访问的。

在创建与第三方库或应用程序中的另一个自定义组件进行交互的自定义组件时,将 forwardRef 模式包含在工作流中非常有帮助。通过授予对库的 DOM 节点或另一个组件的 DOM 实例的访问权,它有助于将对这些组件的控制权传递给你。

以下是 forwardRef 模式的用例示例:

代码语言:javascript复制
import React, { forwardRef, useEffect, useRef } from "react";

const CustomInput = forwardRef((props, ref) => (
  <input type="text" {...props} ref={ref} />
));

const ParentComponent = () => {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <CustomInput ref={inputRef} />;
};

在上面的代码中,我们使用 forwardRefs 从我们的组件<ParentComponent/>触发了另一个组件<CustomInput/>input

后记

「分享是一种态度」

Reference

[1]

{JSON} Placeholder: https://jsonplaceholder.typicode.com/

[2]

Reqres: https://reqres.in/

[3]

publicapis: https://publicapis.io/fruity-vice-food-api

[4]

randomuser: https://randomuser.me/

[5]

boredapi: https://www.boredapi.com/

[6]

开源 API: https://mixedanalytics.com/blog/list-actually-free-open-no-auth-needed-apis/

0 人点赞