整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

React入门:从入口文件到组件化的奥秘

前端开发领域,React和Vue的出现,标志着前端开发者可以“Say goodbye to DOM coding”,即不必再直接与DOM元素频繁互动,他们引领着我们告别传统的DOM操作时代,迈入MVVM(Model-View-ViewModel)模式的崭新天地。

通过上一篇文章 [初识React框架](https://juejin.cn/post/7385752495534522383),我们能知道React是一个由Facebook开发并维护的开源JavaScript库,并以其高效、灵活的组件化思想,成为了构建用户界面的首选工具之一。它通过采用组件化的方式来组织代码,使得复杂的UI结构变得易于管理和维护。本文将从一个简单的React项目入口文件`index.js`出发,逐步揭开React的神秘面纱,探索其核心概念与机制,直至深入组件化的精髓。

一、React入门

我们来到React项目入口文件index.js中,这是React应用的起点。

import ReactDOM from 'react-dom/client';

const root=ReactDOM.createRoot(

document.getElementById('root')

);

root.render(<h1>征程</h1>)

通过import ReactDOM from 'react-dom/client';引入了React的DOM渲染库,而ReactDOM.createRoot()方法则标志着React接管DOM的第一步。这个方法接收一个DOM元素作为参数(如document.getElementById('root')),为React创建了一个挂载点。从这一刻起,React将独立管理这部分DOM,也就是这里将成为React展示的舞台,根节点root 内部就是react的世界了,此后的所有React组件将在这个根节点下进行渲染。root.render()方法接收一个React元素作为参数,并将其渲染到与root关联的DOM节点上。

二、JSX:让JavaScript拥抱HTML

在React的世界里,JSX是最具特色的语法糖。通过允许在JavaScript中直接编写类似HTML的代码,极大地提升了代码的可读性和编写效率。

import ReactDOM from 'react-dom/client';

import React from 'react';

const root=ReactDOM.createRoot(

document.getElementById('root')

);

// react 的法宝 : JSX语法

const element1 = (<h1>正大会</h1>)

const element2 =React.createElement('div',{id:'name'},'正小会')

// root.render(element1)

root.render(element2)

<h1>正大会</h1>这样的JSX表达式,不仅直观地描述了UI结构,而且与React.createElement()相比,更加简洁明了。JSX背后,是编译器将其转换为React元素(如React.createElement('h1', null, '正大会'))的过程,从而确保了JavaScript引擎的正常解析执行。也就是说明,在react中我们只要运用JSX,就可以在JavaScript直接编写HTML。

三、职责分离

React鼓励我们以函数或类的形式定义组件,每个组件负责渲染一块UI,这种设计模式大大促进了代码的复用和维护。

import ReactDOM from 'react-dom/client';

import React from 'react';

// module 模块化的导入

import APP from './app/app';

const root =ReactDOM.createRoot(

// 就做这一次DOM查找,ReactDOM来做查找

// DOM编程 性能很差

document.getElementById('root')

)

root.render(<APP/>)

index.js就用于生成一个root节点,而其他的东西就交给其他的组件APP,但是要注意的是,root.render()接受的是一个参数,所以根组件里必须返回一个值。

// 根组件

// react 使用函数创建一个组件,一定要返回JSX

// 页面由组件组成(更利于复用)

const APP = ()=>{

const element = (

<div className='container'>

<h1> classname='title'>周老板</h1>

</div>

)

return element

}

// module 模块化的输出

export default APP

通过定义const APP = ()=>{...}这样的函数创建一个组件,而在组件中我们可以写一堆的HTML、css、js,而页面将由组件组成,更利于复用。而组件内部可以继续嵌套其他组件(AppHeader组件),如<AppHeader />,形成了一个组件树结构,这便是React应用的核心架构。

import AppHeader from './components/app-header';

const APP = ()=>{

const element = (

<div className='container'>

<AppHeader/>

<AppHeader />

</div>

)

return element

}

export default APP

四、组件化

组件化是React的灵魂。它允许我们将界面拆分成多个独立、可复用的部分,每个部分都有自己的状态和生命周期。

const AppHeader = (props)=>{

const {name}= props;

return (

<div className="app-header">

<h1 className="title">{name}</h1>

</div>

)

}

export default AppHeader

AppHeader组件是可以传入参数的,别忘了他是由函数来创建的。通过属性(props)传递数据(如name),这样就可以获取调用AppHeader的那个组件中的name值,更加利于复用。

import AppHeader from './components/app-header';

const APP = ()=>{

const element = (

<div className='container'>

<AppHeader name='吴彦祖'/>

<AppHeader name='彭于晏'/>

</div>

)

return element

}

export default APP

组件之间可以进行通信,同时保持各自的纯净性,降低了耦合度。组件化不仅提高了代码的组织性和可测试性,也为大规模应用的开发提供了有力支撑。

结语

React不仅仅是一个库,它代表了一种全新的前端开发范式。通过ReactDOM.createRoot与JSX的巧妙结合,React让我们能够在JavaScript中以声明式的方式描述UI,通过组件化思想实现界面的模块化开发。这一系列机制,不仅提升了开发效率,也使得代码更易于理解和维护。不愧是“前端第一框架”,这可不是虚名昂。接下来,我将持续学习并分享,让我们一起加油!!

者 | Lakindu Hewawasam

译者 | 许学文

策划 | 丁晓昀

如果你在开发工作中使用的是 React 框架,那么首当其冲要学习的就是思考如何设计组件。组件设计并非简单地将多个组件合成一个集合,而是需要思考如何设计更小、复用性更强的组件。例如,思考下面这张组件图:



简化的组件图


图中有三个组件,分别是:


  1. Typography 组件
  2. Footer 组件
  3. Sizeable Box 组件


如图所示,Typography 组件同时被 Footer 和 Sizeable Box 组件使用。通常我们以为这样,就能构建一个简单、易维护和易排除错误的应用了。但其实只是这样思考组件的设计是远远不够的。


如果你知道如何从组件的视角思考问题,就可以通过在 React 组件中使用设计模式来提高代码的整体模块性、可扩展性和可维护性。


因此,下面这五种设计模式,是你在使用 React 时必须要掌握的。


模式一:基础组件


首先,在使用 React 时候,请尝试为应用设计基础组件。


基础UI组件,就是一个具备默认行为且支持定制化的组件。


例如,每个应用都会通过基础的样式、按钮设计或者基础的排版,来实现应用在视觉和交互上的一致性。这些组件的设计特点是:


  1. 组件会应用一组默认的配置。因此,使用者无需进行任何额外的配置,就可以快速基于默认配置使用组件。
  2. 组件可以支持定制化,使用者通过定制化可以覆盖组件的默认行为,从而为组件提供自定义的整体视觉和交互效果。


通过一个 Button 组件就能很好地说明基础组件模式的实现。示例如下:


  1. Button 组件可能会有诸如空心、实心等不同形态。
  2. Button 组件可能会有默认的文本。


现在你就可以利用基础组件模式进行设计,使组件的使用者可以改变其行为。请参考我基于基础组件模式完成的Button组件,示例代码如下:


import React, { ButtonHTMLAttributes } from 'react';




// 按钮组件的形态:实心或者空心
type ButtonVariant = 'filled' | 'outlined';




export type ButtonProps = {
  /**
   * the variant of the button to use 
   * @default 'outlined'
   */
  variant?: ButtonVariant;
} & ButtonHTMLAttributes<HTMLButtonElement>;;




const ButtonStyles: { [key in ButtonVariant]: React.CSSProperties } = {
  filled: {
    backgroundColor: 'blue', // Change this to your filled button color
    color: 'white',
  },
  outlined: {
    border: '2px solid blue', // Change this to your outlined button color
    backgroundColor: 'transparent',
    color: 'blue',
  },
};




export function Button({ variant = 'outlined', children, style, ...rest }: ButtonProps) {
  return (
    <button
      type='button'
      style={{
        ...ButtonStyles[variant],
        padding: '10px 20px',
        borderRadius: '5px',
        cursor: 'pointer',
        ...style
      }} {...rest}>
      {children}
    </button>
  );
}

复制代码


仔细观察代码会发现,这里 Button 组件的 props 类型合并了原生 HTML 中 button 标签属性的全部类型。这意味着,使用者除了可以为 Button 组件设置默认配置外,还可以设置诸如 onClick、aria-label 等自定义配置。这些自定义配置会通过扩展运算符传递给 Button 组件内部的 button 标签。


通过不同的上下文设置,可以看到不同的 Button 组件的形态,效果截图如下图。


这个可以查看具体设置:

https://bit.cloud/lakinduhewa/react-design-patterns/base/button/~compositions



基础组件在不同上下文中的使用效果


通过不同的上下文,你可以设定组件的行为。这可以让组件成为更大组件的基础。


模式二:组合组件


在成功创建了基础组件后,你可能会希望基于基础组件创建一些新的组件。


例如,你可以使用之前创建的 Button 组件来实现一个标准的 DeleteButton 组件。通过在应用中使用该 DeleteButton,可以让应用中所有删除操作在颜色、形态以及字体上保持一致。


不过,如果出现重复组合一组组件来实现相同效果的现象,那么你可以考虑将它们封装到一个组件中。


下面,让我们来看看其中一种实现方案:

https://bit.cloud/lakinduhewa/react-design-patterns/composition/delete-button



使用组合模式创建组件


如上面的组件依赖图所示,DeleteButton 组件使用基础的 Button 组件为所有与删除相关的操作提供标准的实现。下面是基本代码实现:


// 这里引入了,基础按钮组件和其props
import { Button, ButtonProps } from '@lakinduhewa/react-design-patterns.base.button';
import React from 'react';




export type DeleteButtonProps = {} & ButtonProps;




export function DeleteButton({ ...rest }: DeleteButtonProps) {
  return (
    <Button
      variant='filled'
      style={{
        background: 'red',
        color: 'white'
      }}
      {...rest}
    >
      DELETE
    </Button>
  );
}

复制代码


我们使用基于模式一创建的 Button 组件来实现的 DeleteButton 组件的效果如下:



现在我们可以在应用中使用统一的删除按钮。此外,如果你使用类似 Bit 的构建系统进行组件的设计和构建,那么当 Button 组件发生改变时,可以让CI服务自动将此改变传递到DeleteButton组件上,就像下面这样(当 Button 组件从 0.0.3 升级到了 0.0.4,那么 CI 服务会自动触发,将 DeleteButton 组件从 0.0.1 升级到 0.0.2):


Bit 上的一个 CI 构建


模式三:使用 Hooks


React Hooks 是React v16就推出来的特性,它不依赖类组件实现状态管理、负效应等概念。简而言之,就是你可以通过利用 Hooks API 摆脱对类组件的使用需求。useSate 和 useEffect 是最广为人知的两个 Hooks API,但本文不打算讨论它们,我想重点讨论如何利用 Hooks 来提高组件的整体可维护性。


例如,请考虑下面这个场景:


  1. 有一个 BlogList 组件。
  2. BlogList 组件会通过调用一个简单的 API,获取博客文章列表数据,同时将其渲染在组件上。


基于上面的案例,你可能会像下面这样将 API 逻辑直接写在函数组件中:


import React, { useState, useEffect } from 'react';
import axios from 'axios';
const BlogList = () => {
    const [blogs, setBlogs] = useState([]);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState(null);
    useEffect(() => {
        axios.get('https://api.example.com/blogs')
            .then(response => {
                setBlogs(response.data);
                setIsLoading(false);
            })
            .catch(error => {
                setError(error);
                setIsLoading(false);
            });
    }, []);
    if (isLoading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;
    return (
        <div>
            <h2>Blog List</h2>
            <ul>
                {blogs.map(blog => (
                    <li key={blog.id}>{blog.title}</li>
                ))}
            </ul>
        </div>
    );
};
export default BlogList;

复制代码


这样写,组件也能正常工作。它将会获取博客文章列表并且渲染在 UI 上。但是,这里将 UI 逻辑和 API 逻辑混在一起了。


理想情况下,React 组件应该不需要关系如何获取数据。而只需要关心接收一个数据数组,然后将其呈现在 DOM 上。


因此,实现这一目标的最佳方法是将 API 逻辑抽象到 React Hook 中,以便在组件内部进行调用。这样做就可以打破 API 调用与组件之间的耦合。通过这种方式,就可以在不影响组件的情况下,修改底层的数据获取逻辑。


其中一种实现方式如下。


1.useBlog hook


import { useEffect, useState } from 'react';
import { Blog } from './blog.type';
import { Blogs } from './blog.mock';




export function useBlog() {
  const [blogs, setBlogs] = useState<Blog[]>([]);
  const [loading, setLoading] = useState<boolean>(false);




  useEffect(() => {
    setLoading(true);
     // 注意:这里的setTimeout非实际需要,只是为了模拟API调用
    setTimeout(() => {
      setBlogs(Blogs);
      setLoading(false);
    }, 3000);
  }, []);




  return { blogs, loading }
}

复制代码


如上代码所示,useBlog hook 获取博客列表数据,然后赋值给状态变量,最后通过导出变量给到消费者(BlogList 组件)使用:



Hook 效果


2.BlogList 组件


import React from 'react';
// 引入上面封装的 useBlog hook
import { useBlog } from '@lakinduhewa/react-design-patterns.hooks.use-blog';
export function BlogList() {
  const { blogs, loading } = useBlog();
  if (loading) {
    return (
      <p>We are loading the blogs...</p>
    )
  }
  return (
    <ul>
      {blogs.map((blog) => <ol
        key={blog.id}
      >
        {blog.title}
      </ol>)}
    </ul>
  );
}

复制代码



BlogList 组件效果


通过调用 useBlog 和使用其导出的状态变量,我们在 BlogList 组件中使用了 Hooks。如此,相对于之前,我们可以减少大量代码,并以最少的代码和精力维护两个组件。


此外,当你使用类似 Bit 这样的构建系统时(就像我一样),只需将 useBlog 组件导入本地开发环境,然后再修改完成之后重新推送回 Bit Cloud。Bit Cloud 的构建服务器可以依托依赖树将此修改传递给整个应用。因此如果只执行一些简单修改,甚至不需要访问整个应用。


模式四:React Providers


此模式的核心是解决组件状态共享。我们都曾是 props 下钻式传递的受害者。但如果你还没有经历过,那这里简单解释下:“props 下钻式传递”就是当你在组件树中进行 props 传递时,这些 props 只会在最底层组件中被使用,而中间层的组件都不会使用该 props。例如,看看下面这张图:

props 下钻式传递


从 BlogListComponent 一直向下传递一个 isLoading 的 props 到 Loader。但是,isLoading 只在 Loader 组件中使用。因此,在这种情况下,组件不但会引入不必要的 props,还会有性能开销。因为当 isLoading 发生变化时,即使组件没有使用它,React 依然会重新渲染你的组件树。


因此,解决方案之一就是通过利用 React Context 来使用 React Context Provider 模式。React Context 是一组组件的状态管理器,通过它,你可以为一组组件创建特定的上下文。通过这种方式,你可以在上下文中定义和管理状态,让不同层级的组件都可以直接访问上下文,并按需使用 props。这样就可以避免 props 下钻式传递了。


主题组件就是该模式的一个常见场景。例如,你需要在应用程序中全局访问主题。但将主题传递到应用中的每个组件并不现实。你可以创建一个包含主题信息的 Context,然后通过 Context 来设置主题。看一下我是如何通过React Context实现主题的,以便更好地理解这一点:

https://bit.cloud/lakinduhewa/react-design-patterns/contexts/consumer-component


import { useContext, createContext } from 'react';
export type SampleContextContextType = {
  /**
   * primary color of theme.
   */
  color?: string;
};
export const SampleContextContext = createContext<SampleContextContextType>({
  color: 'aqua'
});
export const useSampleContext = () => useContext(SampleContextContext);

复制代码


在 Context 中定义了一种主题颜色,它将在所有实现中使用该颜色来设置字体颜色。接下来,我还导出了一个 hook——useSampleContext,该 hook 让消费者可以直接使用 Context。


只是这样还不行,我们还需要定义一个 Provider。Provider 是回答 "我应该与哪些组件共享状态?"问题的组件。Provider的实现示例如下:


import React, { ReactNode } from 'react';
import { SampleContextContext } from './sample-context-context';
export type SampleContextProviderProps = {
  /**
   * primary color of theme.
   */
  color?: string,
  /**
   * children to be rendered within this theme.
   */
  children: ReactNode
};
export function SampleContextProvider({ color, children }: SampleContextProviderProps) {
  return <SampleContextContext.Provider value={{ color }}>{children}</SampleContextContext.Provider>
}

复制代码


Provider 在管理初始状态和设置 Context 可访问状态的组件方面起着至关重要的作用。


接下来,你可以创建一个消费者组件来使用状态:



消费者组件


模式五:条件渲染


最后一个想和大家分享的是条件渲染模式。今天,人人都知道 React 中的条件渲染。它通过条件判断来选择组件进行渲染。


但在实际使用中我们的用法常常是错误的:


// ComponentA.js
const ComponentA = () => {
    return <div>This is Component A</div>;
};
// ComponentB.js
const ComponentB = () => {
    return <div>This is Component B</div>;
};
// ConditionalComponent.js
import React, { useState } from 'react';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
const ConditionalComponent = () => {
    const [toggle, setToggle] = useState(true);
    return (
        <div>
            <button onClick={() => setToggle(!toggle)}>Toggle Component</button>
            {toggle ? <ComponentA /> : <ComponentB />}
        </div>
    );
};
export default ConditionalComponent;

复制代码


你是否注意到,这里我们将基于条件的逻辑耦合到了 JSX 代码片段中。通常,你不应该在 JSX 代码中中添加任何与计算相关的逻辑,而只将与 UI 渲染相关的内容放在其中。


解决这个问题的方法之一是使用条件渲染组件模式。创建一个可重用的 React 组件,该组件可以根据条件渲染两个不同的组件。它的实现过程如下:


import React, { ReactNode } from 'react';
export type ConditionalProps = {
  /**
   * the condition to test against
   */
  condition: boolean
  /**
   * the component to render when condition is true
   */
  whenTrue: ReactNode
  /**
   * the component to render when condition is false
   */
  whenFalse: ReactNode
};
export function Conditional({ condition, whenFalse, whenTrue }: ConditionalProps) {
  return condition ? whenTrue : whenFalse;
}

复制代码


我们创建了一个可以按条件渲染两个组件的组件。当我们将其集成到其他组件中时,会使代码更简洁,因为无需在 React 组件中加入复杂的渲染逻辑。你可以像下面这样使用它:


export const ConditionalTrue = () => {
  return (
    <Conditional
      condition
      whenFalse="You're False"
      whenTrue="You're True"
    />
  );
}
export const ConditionalFalse = () => {
  return (
    <Conditional
      condition={false}
      whenFalse="You're False"
      whenTrue="You're True"
    />
  );
}

复制代码


实际的输入如下:



总结


掌握这五种设计模式,为 2024 年做好充分准备,构建出可扩展和可维护的应用吧。


如果你想详细深入本文中讨论的模式,请随时查看我在Bit Cloud的空间:

https://bit.cloud/lakinduhewa/react-design-patterns


感谢你的阅读!

原文链接:2024年,你应该知道的5种React设计模式_架构/框架_InfoQ精选文章



题描述:

  • 图片的路径是正确的;
  • 在原生html中写是没有任何问题的;
  • 但是当我们通过img标签中的src引用图片但是不显示;


  • 代码是这样子的:

src写的图片路径但是不显示图片


通过在state中定义,也不显示



解决方法:

  • 通过import解决


  • 通过require解决


需要注意的是,require里只能写字符串,不能写变量。

  • 结果




为什么会出现这种情况?

  • create-react-app的官网,官网明确说出最好将图片样式等放在public文件夹中,是可以用的<img src="../images/photo.png"/>这种形式的;

官网链接:https://facebook.github.io/create-react-app/docs/using-the-public-folder#when-to-use-the-public-folder






加油,程序员。。。