嘿,2天就能入门React啦,再多时间我可真没啦!

JSX 语法学习知识点(第 8 - 11 天)

第 8 天:JSX 基本语法

元素创建

JSX 允许我们像编写 HTML 一样创建 React 元素,它本质上是 JavaScript 的语法扩展。例如:

const element = <h1>Hello, JSX!</h1>;

这种写法既简洁又直观,React 会将 JSX 编译为 React.createElement() 函数调用,上述代码会被编译为:

const element = React.createElement('h1', null, 'Hello, JSX!');

属性定义

在 JSX 中定义元素属性时,有些属性名与 HTML 中的有所不同,需要特别注意:

  • HTML 中的 class 属性在 JSX 中需写为 className,因为 class 是 JavaScript 的关键字。例如:<div className="container">Content</div>
  • HTML 中的 for 属性在 JSX 中需写为 htmlFor,同理,for 也是 JavaScript 的关键字。例如:<label htmlFor="username">Username:</label>
  • 其他大多数属性名与 HTML 一致,如 idsrchref 等,例如:<img src="image.jpg" alt="Example" /><a href="https://example.com">Link</a>

嵌入变量和表达式

在 JSX 中,我们可以通过 {} 来嵌入变量、表达式,实现动态内容渲染。

嵌入变量

先定义变量,然后在 JSX 中用 {} 包裹变量名。例如:

const name = "Alice";
const element = <h1>Hello, {name}!</h1>; // 渲染结果为 <h1>Hello, Alice!</h1>
嵌入表达式

可以在 {} 中放入任意的 JavaScript 表达式,如算术运算、函数调用等。例如:

const a = 10;
const b = 20;
const element = <p>Sum: {a + b}</p>; // 渲染结果为 <p>Sum: 30</p>

function getGreeting(name) {
  return `Hello, ${name}!`;
}
const greetingElement = <h1>{getGreeting("Bob")}</h1>; // 渲染结果为 <h1>Hello, Bob!</h1>

第 9 天:JSX 条件渲染和列表渲染基础

条件渲染方式

在 JSX 中,我们可以根据不同的条件渲染不同的内容,常用的方式有以下几种:

  • 三元表达式:通过 condition ? exprIfTrue : exprIfFalse 的形式进行条件判断。例如:

    const isLoggedIn = true;
    const element = isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>;
    // 当 isLoggedIn 为 true 时渲染 <p>Welcome back!</p>,否则渲染 <p>Please log in.</p>
    
  • 逻辑与运算符(&&):当条件为真时,渲染后面的元素;当条件为假时,不渲染。例如:

    const hasMessage = true;
    const message = "This is a message";
    const element = hasMessage && <p>{message}</p>;
    // 当 hasMessage 为 true 时渲染 <p>This is a message</p>,否则不渲染
    
  • 变量赋值:先根据条件将不同的元素赋值给变量,然后在 JSX 中使用该变量。例如:

    let element;
    const isAdmin = false;
    if (isAdmin) {
      element = <p>Admin Dashboard</p>;
    } else {
      element = <p>User Dashboard</p>;
    }
    

列表渲染基础

使用 map 方法遍历简单数组,可以生成 JSX 元素列表。例如:

const fruits = ["Apple", "Banana", "Orange"];
const fruitList = fruits.map((fruit) => <li>{fruit}</li>);
const element = <ul>{fruitList}</ul>;

上述代码会渲染出一个包含三个列表项的无序列表,每个列表项分别显示数组中的水果名称。map 方法会对数组中的每个元素进行处理,返回一个新的 JSX 元素,最终将这些元素组成的数组嵌入到 <ul> 元素中。

第 10 天:深入学习列表渲染

为元素添加唯一 key 属性

在使用 map 方法进行列表渲染时,需要为每个生成的元素添加唯一的 key 属性。key 是 React 用于识别列表中元素的唯一标识,它帮助 React 高效地更新 DOM。例如:

const fruits = [
  { id: 1, name: "Apple" },
  { id: 2, name: "Banana" },
  { id: 3, name: "Orange" }
];
const fruitList = fruits.map((fruit) => (
  <li key={fruit.id}>{fruit.name}</li>
));
const element = <ul>{fruitList}</ul>;

这里使用水果对象的 id 作为 key,因为 id 是唯一的。如果数组中没有唯一的 id,也可以使用元素的索引作为 key,但不推荐在列表可能重新排序的情况下使用索引作为 key,因为这可能会导致性能问题和状态错误。

key 的作用和重要性

  • 作用key 帮助 React 确定哪些元素发生了变化、被添加或被移除,从而避免不必要的 DOM 操作,提高渲染性能。
  • 重要性:如果没有正确设置 key,React 可能会错误地更新 DOM 元素,导致界面显示异常。例如,当列表中的元素顺序发生变化时,没有 keykey 不唯一会导致 React 无法准确识别元素,可能会重新创建所有元素,而不是只移动它们的位置。

处理复杂数据结构的列表渲染

当数组中的元素是复杂对象(包含嵌套对象或数组)时,我们可以通过逐层访问对象的属性来进行渲染。例如:

const users = [
  {
    id: 1,
    name: "Alice",
    contact: {
      email: "alice@example.com",
      phone: "123-456-7890"
    },
    hobbies: ["Reading", "Music"]
  },
  {
    id: 2,
    name: "Bob",
    contact: {
      email: "bob@example.com",
      phone: "987-654-3210"
    },
    hobbies: ["Sports", "Gaming"]
  }
];
const userList = users.map((user) => (
  <div key={user.id} className="user-card">
    <h3>{user.name}</h3>
    <p>Email: {user.contact.email}</p>
    <p>Phone: {user.contact.phone}</p>
    <p>Hobbies:</p>
    <ul>
      {user.hobbies.map((hobby, index) => (
        <li key={index}>{hobby}</li>
      ))}
    </ul>
  </div>
));
const element = <div>{userList}</div>;

在这个例子中,我们遍历 users 数组,每个用户对象包含 contact 嵌套对象和 hobbies 数组。我们通过 user.contact.email 访问嵌套对象的属性,通过再次使用 map 方法遍历 user.hobbies 数组来渲染爱好列表,这里因为 hobbies 数组没有唯一 id,所以使用索引作为 key

第 11 天:JSX 样式添加和注释写法

内联样式添加

在 JSX 中,内联样式通过对象形式定义,并且使用 style={{}} 来应用。样式对象的属性名采用驼峰式命名法(如 fontSize 而不是 font-size)。例如:

const style = {
  color: "blue",
  fontSize: "18px",
  fontWeight: "bold",
  backgroundColor: "#f0f0f0",
  padding: "10px"
};
const element = <div style={style}>This has inline styles</div>;

也可以直接在 style 属性中定义样式对象:

const element = (
  <p style={{ color: "red", textDecoration: "underline" }}>
    This is a styled paragraph
  </p>
);

外部样式添加

我们可以将样式定义在外部 CSS 文件中,然后通过 import 导入,再使用 className 引用样式类。

首先创建外部 CSS 文件(如 styles.css):

.container {
  width: 80%;
  margin: 0 auto;
  border: 1px solid #ccc;
  padding: 20px;
}
.title {
  color: green;
  text-align: center;
}

然后在 JSX 文件中导入并使用:

import './styles.css';

const element = (
  <div className="container">
    <h2 className="title">Using External Styles</h2>
    <p>This is content with external styles.</p>
  </div>
);

JSX 中的注释写法

在 JSX 中,注释需要写在 {/* */} 中。例如:

const element = (
  <div>
    {/* 这是一个注释 */}
    <h1>Hello, JSX!</h1>
    {/* 
      这是一个
      多行注释
    */}
    <p>This is a paragraph.</p>
  </div>
);

需要注意的是,注释必须写在 JSX 元素内部或元素之间,不能写在 {} 外部的 JavaScript 代码区域。

React 组件学习知识点(第 11 - 15 天)

第 11 天:函数组件的定义、导出导入与使用

函数组件的定义

函数组件是使用 JavaScript 函数定义的 React 组件,它是 React 中最基础也最常用的组件形式之一。

  • 通过 function 关键字创建:函数名即为组件名,且组件名必须首字母大写,这是 React 识别组件的约定。函数可以返回 JSX 元素作为组件内容。例如:

    function Button() {
      return <button>Click Me</button>;
    }
    
  • 通过箭头函数创建:箭头函数创建的组件同样需要遵循首字母大写的命名规范,返回值为 JSX 元素。例如:

    const Text = () => {
      return <p>This is a text component</p>;
    };
    

组件的导出与导入

导出组件:要在其他文件中使用组件,需要先将组件导出。常用的导出方式有默认导出(export default)和命名导出(export)。

  • 默认导出:一个文件中只能有一个默认导出,导入时可以自定义组件名。例如:

    // Button.js
    function Button() {
      return <button>Click Me</button>;
    }
    export default Button;
    
  • 命名导出:一个文件中可以有多个命名导出,导入时必须使用对应的名称。例如:

    // Text.js
    export const Text = () => {
      return <p>This is a text component</p>;
    };
    

导入组件:在需要使用组件的文件中,通过 import 语句导入组件。

  • 导入默认导出的组件:

    // App.js
    import Button from './Button';
    
  • 导入命名导出的组件:需要使用大括号包裹组件名。

    // App.js
    import { Text } from './Text';
    

在父组件中使用子组件

在父组件中,导入子组件后,就可以像使用 HTML 标签一样使用子组件。例如,在 App 父组件中使用 Button 和 Text 子组件:

// App.js
import Button from './Button';
import { Text } from './Text';

function App() {
  return(
    <div className="app">
      <Text />
      <Button />
    </div>
  );
}
export default App;

上述代码中,App 是父组件,Text 和 Button 是子组件,父组件通过在 JSX 中写入子组件标签的方式使用子组件,实现了简单的信息展示区域。

第 12 天:类组件的定义、导出导入、使用及与函数组件的对比

类组件的定义

类组件是使用 ES6 类语法定义的 React 组件,它需要继承 React.Component 类,并在类中实现 render() 方法,render() 方法返回组件的 JSX 内容。例如:

import React from 'react';

class ClassButton extends React.Component {
  render() {
    return <button>Class Component Button</button>;
  }
}

类组件的导出与导入

类组件的导出和导入方式与函数组件类似。

  • 导出类组件:

    • 默认导出:
      // ClassButton.js
      import React from 'react';
      
      class ClassButton extends React.Component {
        render() {
          return <button>Class Component Button</button>;
        }
      }
      export default ClassButton;
      
    • 命名导出:
      // ClassText.js
      import React from 'react';
      
      export class ClassText extends React.Component {
        render() {
          return <p>Class Component Text</p>;
        }
      }
      
  • 导入类组件:

    • 导入默认导出的类组件:
      // App.js
      import ClassButton from './ClassButton';
      
    • 导入命名导出的类组件:
      // App.js
      import { ClassText } from './ClassText';
      

类组件的使用

在父组件中使用类组件的方式与使用函数组件相同,导入后在 JSX 中使用组件标签。例如:

// App.js
import ClassButton from './ClassButton';
import { ClassText } from './ClassText';

function App() {
  return (
    <div>
      <ClassText />
      <ClassButton />
    </div>
  );
}

函数组件和类组件的异同

相同点:
  • 都是 React 组件,用于构建 UI 界面,都可以接收 Props 传递数据。
  • 都遵循 React 的组件生命周期相关特性(函数组件通过 Hooks 实现类似功能)。
  • 都需要遵循组件命名规范(首字母大写),且返回的 JSX 结构需有一个根元素。
不同点:
特性函数组件类组件
定义方式使用函数定义使用类定义且继承 React.Component
状态管理通过 Hooks(如 useState)管理状态通过 this.statethis.setState() 管理
生命周期通过 useEffect 等 Hooks 实现有明确的生命周期方法(如 componentDidMount
this 指向没有 thisthis 指向组件实例,需注意绑定
代码简洁性代码更简洁代码相对繁琐

第 13 - 14 天:组件的拆分原则与嵌套组合

组件的拆分原则

将复杂页面拆分为多个小型组件时,需遵循以下原则:

  • 单一职责原则:一个组件只负责完成一个特定的功能或展示一块相关的内容,避免组件功能过于庞大和复杂。例如,电商首页中的导航栏只负责导航功能,不应包含商品展示功能。
  • 复用性原则:拆分出的组件应具有一定的复用性,能够在应用的多个地方被使用。例如,按钮组件、输入框组件等基础组件可以在多个页面中复用。
  • 逻辑性原则:根据业务逻辑和数据关系进行拆分,将相关的逻辑和数据放在同一个组件中。例如,商品列表组件应包含商品数据的展示和相关操作逻辑。
  • UI 相关性原则:将视觉上紧密相关的元素放在同一个组件中,保持 UI 结构的完整性。例如,商品卡片组件应包含商品图片、名称、价格等视觉上相关的元素。

复杂页面的拆分示例

以电商首页为例,可将其拆分为以下组件:

  • 导航栏组件(Navbar):包含网站 logo、导航菜单、搜索框、购物车图标等。
  • 轮播图组件(Carousel):展示首页的轮播图片和相关链接。
  • 商品列表组件(ProductList):展示多个商品卡片,负责商品数据的遍历和展示。
  • 商品卡片组件(ProductCard):作为 ProductList 的子组件,展示单个商品的图片、名称、价格、添加购物车按钮等。
  • 页脚组件(Footer):包含网站版权信息、联系方式、链接等。

组件的嵌套组合

拆分后的组件通过嵌套组合的方式形成完整的页面。父组件可以包含多个子组件,子组件也可以包含自己的子组件,形成组件树结构。例如,电商首页组件(HomePage)的嵌套组合:

// HomePage.js
import Navbar from './Navbar';
import Carousel from './Carousel';
import ProductList from './ProductList';
import Footer from './Footer';

function HomePage() {
  return (
    <div className="home-page">
      <Navbar />
      <Carousel />
      <ProductList />
      <Footer />
    </div>
  );
}

// ProductList.js
import ProductCard from './ProductCard';

function ProductList() {
  const products = [/* 商品数据 */];
  return (
    <div className="product-list">
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

在上述代码中,HomePage 是父组件,包含 Navbar、Carousel、ProductList、Footer 子组件;ProductList 作为父组件,包含多个 ProductCard 子组件,通过这种嵌套组合方式实现了电商首页的完整页面结构。

第 15 天:组件的命名规范和文件组织方式

组件的命名规范

  • 组件名必须首字母大写:这是 React 区分组件和普通 HTML 标签的重要约定,React 会将首字母大写的标签识别为组件。例如 ButtonProductCard 是合法的组件名,buttonproductCard 则会被当作普通标签处理。
  • 使用 PascalCase 命名法:组件名由多个单词组成时,每个单词的首字母都大写,如 UserProfileShoppingCart,使组件名更具可读性。
  • 组件名应清晰表意:组件名应准确反映组件的功能或用途,让人一眼就能了解组件的作用。例如 Navbar 表示导航栏组件,LoginForm 表示登录表单组件。

组件的文件组织方式

  • 一个组件对应一个文件:通常情况下,每个组件单独放在一个文件中,方便管理和维护。例如 Button.js 文件中只包含 Button 组件。
  • 文件名与组件名一致:文件名采用与组件名相同的 PascalCase 命名法,使文件和组件之间的对应关系清晰明了。例如 Button 组件放在 Button.js 文件中,ProductList 组件放在 ProductList.js 文件中。
  • 按功能模块组织文件夹:将相关的组件放在同一个文件夹中,形成功能模块。例如,电商应用中可以创建 components/navbarcomponents/productcomponents/footer 等文件夹,分别存放导航栏相关组件、商品相关组件、页脚相关组件。

index.js 在组件导入中的便捷作用

在组件文件夹中创建 index.js 文件,可以简化组件的导入路径。index.js 文件通常用于导出文件夹中的主要组件,这样在导入组件时,只需指定文件夹路径,而无需指定具体的组件文件路径。例如:

组件文件夹结构:

components/
  Button/
    Button.js
    index.js

Button.js 文件:

function Button() {
  return <button>Click Me</button>;
}
export default Button;

index.js 文件:

export { default } from './Button';

导入组件时:

// 无需写成 import Button from './components/Button/Button';
import Button from './components/Button';

通过这种方式,当组件文件路径发生变化时,只需修改 index.js 文件中的导出路径,而无需修改所有导入该组件的地方,提高了代码的可维护性。

React Props 与 State 学习知识点(第 18 - 25 天)

第 18 - 19 天:深入学习 Props

父组件向子组件传递 Props

Props 是组件间传递数据的主要方式,由父组件向子组件单向传递。传递方式为在子组件标签上添加属性,属性值可以是任意 JavaScript 数据类型(字符串、数字、对象、数组、函数等)。

  • 传递基础类型
// 父组件
function Parent() {
  const name = "React";
  const version = 18;
  return (
    <Child 
      title="Props 示例" 
      libName={name} 
      currentVersion={version} 
    />
  );
}

// 子组件
function Child(props) {
  return (
    <div>
      <h3>{props.title}</h3>
      <p>库名:{props.libName}</p>
      <p>版本:{props.currentVersion}</p>
    </div>
  );
}
  • 传递复杂类型(对象/数组)
// 父组件
function Parent() {
  const user = { name: "Alice", age: 25 };
  const skills = ["React", "JavaScript", "CSS"];
  return <Child userInfo={user} skillList={skills} />;
}

// 子组件
function Child(props) {
  return (
    <div>
      <p>姓名:{props.userInfo.name}</p>
      <p>技能:{props.skillList.join(", ")}</p>
    </div>
  );
}
  • 传递函数(实现子组件向父组件通信)
// 父组件
function Parent() {
  const handleClick = (message) => {
    console.log("子组件传递的消息:", message);
  };
  return <Child onButtonClick={handleClick} />;
}

// 子组件
function Child(props) {
  return (
    <button onClick={() => props.onButtonClick("Hello Parent")}>
      点击向父组件发送消息
    </button>
  );
}

子组件接收 Props 的方式

  • 直接使用 props 参数:子组件函数的第一个参数即为 props 对象,通过 . 语法访问属性。
  • 解构赋值:简化代码,直接提取需要的属性(推荐)。
// 解构单个属性
function Child({ title }) {
  return <h3>{title}</h3>;
}

// 解构多个属性
function Child({ userInfo, skillList }) {
  return (
    <div>
      <p>姓名:{userInfo.name}</p>
      <p>技能:{skillList.join(", ")}</p>
    </div>
  );
}

Props 是只读的特性

React 规定 Props 是只读的(read-only),子组件绝对不能修改接收的 props。如果需要修改数据,应通过父组件更新后重新传递,否则会导致不可预测的 bug。

// 错误示例:子组件试图修改 props
function Child(props) {
  const handleClick = () => {
    props.count = props.count + 1; // 报错!Props 不可修改
  };
  return <button onClick={handleClick}>错误修改</button>;
}

第 20 - 21 天:Props 的默认值与类型检查

Props 默认值(defaultProps)

当父组件未传递某个 props 时,可通过 defaultProps 设置默认值,确保组件在缺少 props 时仍能正常工作。

  • 函数组件设置默认值
function ProductCard({ name, price }) {
  return (
    <div>
      <p>商品名:{name}</p>
      <p>价格:{price}</p>
    </div>
  );
}

// 设置默认值
ProductCard.defaultProps = {
  name: "未知商品",
  price: 0
};

// 使用时未传递 props,将显示默认值
<ProductCard /> // 商品名:未知商品,价格:0 元
  • 类组件设置默认值
class ProductCard extends React.Component {
  render() {
    const { name, price } = this.props;
    return (
      <div>
        <p>商品名:{name}</p>
        <p>价格:{price}</p>
      </div>
    );
  }
}

// 类组件通过静态属性设置默认值
ProductCard.defaultProps = {
  name: "未知商品",
  price: 0
};

Props 类型检查(PropTypes)

使用 prop-types 库可以对 props 的类型进行校验,在开发环境中捕获类型错误,提高代码健壮性。

  • 安装依赖
npm install prop-types --save
  • 函数组件类型检查
import PropTypes from 'prop-types';

function User({ name, age, isStudent }) {
  return (
    <div>
      <p>姓名:{name}</p>
      <p>年龄:{age}</p>
      <p>是否学生:{isStudent ? "是" : "否"}</p>
    </div>
  );
}

// 类型检查配置
User.propTypes = {
  name: PropTypes.string.isRequired, // 字符串类型,必须传递
  age: PropTypes.number, // 数字类型
  isStudent: PropTypes.bool // 布尔类型
};
  • 常用类型检查规则
Component.propTypes = {
  // 基础类型
  str: PropTypes.string,
  num: PropTypes.number,
  bool: PropTypes.bool,
  func: PropTypes.func,
  arr: PropTypes.array,
  obj: PropTypes.object,

  // 特定结构的对象
  user: PropTypes.shape({
    name: PropTypes.string,
    age: PropTypes.number
  }),

  // 枚举类型(只能是指定值)
  status: PropTypes.oneOf(['active', 'inactive', 'deleted']),

  // 多种类型之一
  id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

  // 必须传递
  requiredProp: PropTypes.any.isRequired
};
  • 类组件类型检查:与函数组件写法一致,在类外部通过 类名.propTypes 定义。

第 22 - 23 天:函数组件中 State 的使用(useState)

useState 基本用法

useState 是 React 提供的 Hook,用于在函数组件中管理状态(State)。状态是组件内部维护的数据,变化时会触发组件重新渲染。

  • 定义状态:调用 useState(初始值),返回一个数组 [当前状态, 更新函数]
import { useState } from 'react';

function Counter() {
  // 定义计数状态,初始值为 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>1</button>
    </div>
  );
}
  • 初始值的特殊性:初始值只在组件首次渲染时生效,后续渲染会忽略。如果初始值需要通过计算得到,可传递函数(惰性初始化):
// 惰性初始化:函数返回值作为初始值,只执行一次
const [count, setCount] = useState(() => {
  return localStorage.getItem('count') || 0; // 从本地存储读取初始值
});

状态更新的方式

  • 直接传递新值:适用于简单类型(数字、字符串、布尔值)。
setCount(10); // 直接设置新值
setName("Bob");
  • 基于前一个状态计算新值:当新状态依赖于旧状态时,应传递更新函数(确保获取最新状态)。
// 错误示例:可能获取到旧状态
<button onClick={() => setCount(count + 1)}>1</button>

// 正确示例:通过函数获取前一个状态
<button onClick={() => setCount(prevCount => prevCount + 1)}>1</button>
  • 更新对象/数组状态:状态为对象或数组时,必须返回新的引用(不能直接修改原状态),否则 React 可能无法检测到变化。
// 管理对象状态
const [user, setUser] = useState({ name: "Alice", age: 25 });

// 正确更新:复制旧对象,修改属性(使用展开运算符)
setUser(prev => ({ ...prev, age: prev.age + 1 }));

// 管理数组状态
const [todos, setTodos] = useState([]);

// 正确添加元素:返回新数组
setTodos(prev => [...prev, "新任务"]);

// 正确删除元素:过滤出需要保留的元素(返回新数组)
setTodos(prev => prev.filter(item => item !== "要删除的任务"));

状态更新触发重渲染

当调用更新函数(如 setCount)时,React 会将新状态存入队列,然后重新渲染组件,使用新状态计算 UI。这是组件响应用户交互的核心机制。

function Toggle() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        {isVisible ? "隐藏" : "显示"}
      </button>
      {isVisible && <p>我是可以显示/隐藏的内容</p>}
    </div>
  );
}
// 点击按钮时,isVisible 状态切换,组件重新渲染,内容显示或隐藏

第 24 - 25 天:类组件中 State 的使用

类组件中定义状态(this.state)

类组件通过 this.state 定义初始状态,必须在 constructor 中初始化,且值为对象。

class Counter extends React.Component {
  constructor(props) {
    super(props); // 必须调用父类构造函数
    // 初始化状态
    this.state = {
      count: 0,
      message: "Hello"
    };
  }

  render() {
    return <p>计数:{this.state.count}</p>;
  }
}

更新状态(this.setState())

类组件通过 this.setState() 方法更新状态,不能直接修改 this.state(如 this.state.count = 1 是错误的)。

  • 基本用法:传递对象或函数,React 会合并新状态到 this.state
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleIncrement = () => {
    // 传递对象更新状态
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>计数:{this.state.count}</p>
        <button onClick={this.handleIncrement}>1</button>
      </div>
    );
  }
}
  • this.setState() 是异步操作:React 会批量处理状态更新,确保性能优化。因此,不能在 setState 后立即获取最新状态。
// 错误示例:无法获取最新状态
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 输出旧值,因为 setState 是异步的
  • 依赖前一个状态的更新:当新状态依赖旧状态时,必须传递函数(与 useState 类似),函数接收前一个状态和 props,返回新状态。
// 正确:通过函数获取前一个状态
this.setState((prevState, props) => {
  return { count: prevState.count + 1 };
});
  • 更新对象状态setState 会合并对象(只更新指定属性,其他属性保留):
this.state = {
  user: { name: "Alice", age: 25 },
  theme: "light"
};

// 只更新 user 的 age,其他状态不变
this.setState({
  user: { ...this.state.user, age: 26 } // 注意:需要手动复制旧对象
});

类组件与函数组件状态管理对比

特性函数组件(useState)类组件(this.state)
定义方式const [state, setState] = useState(0)this.state = { count: 0 }(构造函数)
更新方式setState(newValue) 或函数式更新this.setState({ count: 1 }) 或函数式更新
状态合并不会自动合并,需手动处理对象/数组自动合并顶层属性(深层对象需手动合并)
this 依赖无(无需处理 this 绑定)需注意函数的 this 绑定(如箭头函数)
代码简洁性更简洁,减少模板代码相对繁琐,需遵循类语法规范

以上内容详细覆盖了 Props 和 State 的核心知识点,包括传递方式、特性、状态管理方法等。建议结合每日实践任务动手练习,尤其是状态更新的正确方式(避免直接修改)和 Props 类型检查的实际应用,这对编写健壮的 React 代码非常重要。如果有具体场景的疑问,可以随时补充说明!

React Router 学习知识点(第 26 - 31 天)

第 26 天:React Router 基础与基本路由配置

安装 React Router

React Router 是 React 生态中用于处理路由的库,最常用的是适用于 Web 应用的 react-router-dom。安装命令:

npm install react-router-dom

核心组件及作用

React Router 的核心功能通过以下组件实现:

  1. BrowserRouter:路由的根组件,用于包裹整个应用,维护路由历史记录(基于 HTML5 History API)。
    通常在入口文件(如 index.js)中使用,包裹 App 组件:

    import { BrowserRouter } from 'react-router-dom';
    
    ReactDOM.render(
      <BrowserRouter>
        <App />
      </BrowserRouter>,
      document.getElementById('root')
    );
    
  2. Route:定义路由规则,通过 path 属性指定 URL 路径,component 属性指定对应路径渲染的组件。
    示例:

    import { Route } from 'react-router-dom';
    import Home from './pages/Home';
    import List from './pages/List';
    
    function App() {
      return (
        <div>
          {/* 当 URL 为 '/' 时,渲染 Home 组件 */}
          <Route path="/" component={Home} />
          {/* 当 URL 为 '/list' 时,渲染 List 组件 */}
          <Route path="/list" component={List} />
        </div>
      );
    }
    
  3. Link:用于创建路由链接,类似 HTML 中的 <a> 标签,但不会导致页面刷新(单页应用特性)。
    通过 to 属性指定跳转路径:

    import { Link } from 'react-router-dom';
    
    function Navbar() {
      return (
        <nav>
          <Link to="/">首页</Link>
          <Link to="/list">列表页</Link>
        </nav>
      );
    }
    

基本路由配置示例

实现首页、列表页的路由跳转:

// App.js
import { Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import List from './pages/List';

function App() {
  return (
    <div>
      {/* 导航链接 */}
      <nav>
        <Link to="/">首页</Link> | 
        <Link to="/list">列表页</Link>
      </nav>

      {/* 路由规则 */}
      <Route path="/" component={Home} />
      <Route path="/list" component={List} />
    </div>
  );
}

第 27 天:Route 精确匹配与 Switch 组件

Route 的 path 和 component 属性

  • path:定义路由匹配的 URL 路径(字符串),如 //list
  • component:指定路径匹配时渲染的组件,组件会接收路由相关的 props(如 historylocationmatch)。

精确匹配(exact 属性)

默认情况下,Routepath 是“包含匹配”(只要 URL 包含 path 就会渲染)。例如,path="/" 会匹配所有 URL(因为所有 URL 都以 / 开头),导致多个组件同时渲染。
使用 exact 属性可开启“精确匹配”,只有 URL 完全等于 path 时才渲染:

// 仅当 URL 严格为 '/' 时,才渲染 Home 组件
<Route exact path="/" component={Home} />
// URL 为 '/list' 时渲染 List 组件(不受 exact 影响,因为没有子路径)
<Route path="/list" component={List} />

Switch 组件的作用

Switch 组件用于包裹多个 Route只渲染第一个匹配的路由,解决路由匹配的优先级问题(避免多个路由同时渲染)。
示例:

import { Route, Switch } from 'react-router-dom';

function App() {
  return (
    <Switch>
      {/* 精确匹配 '/',优先级最高 */}
      <Route exact path="/" component={Home} />
      {/* 匹配 '/list' */}
      <Route path="/list" component={List} />
      {/* 若以上路由都不匹配,渲染 404 组件(放最后) */}
      <Route component={NotFound} />
    </Switch>
  );
}

注意:Switch 内的 Route 匹配顺序从前往后,应将更具体的路径(如 /list)放在前面,模糊路径(如 /)放在后面。

第 28 - 29 天:动态路由参数

定义动态路由参数

Routepath 中,通过 :paramName 定义动态参数(如 ID、名称等),用于匹配变化的 URL 片段。
示例:定义商品详情页路由,接收 id 参数:

// 动态参数 id 表示商品唯一标识
<Route path="/product/:id" component={ProductDetail} />

此时,URL 如 /product/123/product/456 都会匹配该路由,123456 即为动态参数 id 的值。

通过 useParams 获取动态参数

在函数组件中,使用 useParams Hook 从动态路由中提取参数。步骤:

  1. react-router-dom 导入 useParams
  2. 在组件中调用 useParams(),返回包含参数的对象。

示例(商品详情页):

import { useParams } from 'react-router-dom';

function ProductDetail() {
  // 获取动态参数 id
  const { id } = useParams();

  return (
    <div>
      <h2>商品详情</h2>
      <p>商品 ID{id}</p>
      {/* 根据 id 请求对应商品数据并渲染 */}
    </div>
  );
}

实现详情页跳转

在列表页中,通过 Link 跳转至详情页,并传递动态参数:

// 商品列表页
function ProductList() {
  const products = [
    { id: 1, name: "商品 A" },
    { id: 2, name: "商品 B" }
  ];

  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          {/* 跳转至详情页,传递 id 参数 */}
          <Link to={`/product/${product.id}`}>
            {product.name}
          </Link>
        </li>
      ))}
    </ul>
  );
}

第 30 天:嵌套路由

嵌套路由的概念

嵌套路由用于在父组件中嵌入子路由,实现页面局部刷新(父组件不变,子组件切换)。例如:首页(父组件)中包含推荐模块、热门模块(子组件),通过子路由切换显示。

配置嵌套路由的步骤

  1. 父组件中定义子路由:在父组件的 JSX 中,使用 Route 定义子路由,path 为“父路径/子路径”。
  2. 父组件中预留子路由渲染位置:子组件会渲染在父组件中 Route 所在的位置。

示例:首页(Home)中嵌套推荐(Recommend)和热门(Hot)子模块:

// 父组件:Home.js
import { Route, Link, Switch } from 'react-router-dom';
import Recommend from './Recommend';
import Hot from './Hot';

function Home() {
  return (
    <div>
      <h2>首页</h2>
      {/* 子路由导航 */}
      <div>
        <Link to="/home/recommend">推荐</Link> |
        <Link to="/home/hot">热门</Link>
      </div>
      {/* 子路由渲染位置 */}
      <Switch>
        <Route path="/home/recommend" component={Recommend} />
        <Route path="/home/hot" component={Hot} />
      </Switch>
    </div>
  );
}

// 根路由配置(App.js)
<Route path="/home" component={Home} />

此时,访问 /home/recommend 会在 Home 组件中渲染 Recommend 组件,访问 /home/hot 会渲染 Hot 组件。

第 31 天:编程式导航与路由守卫

编程式导航(useHistory Hook)

useHistory Hook 用于在函数组件中通过代码实现路由跳转(非 Link 标签跳转),返回的 history 对象包含以下常用方法:

  • push(path):跳转到指定路径(新增历史记录,可通过浏览器回退返回)。
  • replace(path):替换当前路径(不新增历史记录,无法回退到上一页)。
  • goBack():回退到上一页。

示例:

import { useHistory } from 'react-router-dom';

function Login() {
  const history = useHistory();

  const handleLogin = () => {
    // 模拟登录成功
    localStorage.setItem('token', 'xxx');
    // 跳转到首页(编程式导航)
    history.push('/home');
    // 若需要替换当前页(如登录页不希望回退),可使用:
    // history.replace('/home');
  };

  return (
    <div>
      <button onClick={handleLogin}>登录</button>
      <button onClick={() => history.goBack()}>返回</button>
    </div>
  );
}

路由守卫的基本概念

路由守卫用于控制路由的访问权限(如未登录用户不能访问个人中心),在路由跳转前进行验证,符合条件才允许访问,否则跳转至登录页等。

用高阶组件实现简单路由守卫

高阶组件(HOC)是一个函数,接收组件作为参数,返回一个新组件。通过 HOC 实现登录验证守卫:

import { Redirect, Route } from 'react-router-dom';

// 定义路由守卫高阶组件
function withAuth(Component) {
  return function AuthComponent(props) {
    // 检查是否登录(从本地存储获取 token)
    const isLogin = !!localStorage.getItem('token');
    if (isLogin) {
      // 已登录,渲染目标组件
      return <Component {...props} />;
    } else {
      // 未登录,重定向到登录页
      return <Redirect to="/login" />;
    }
  };
}

// 使用守卫保护个人中心路由
<Route 
  path="/profile" 
  component={withAuth(Profile)} // 只有登录后才能访问 Profile 组件
/>

说明:Redirect 组件用于路由重定向,to 属性指定跳转路径。

以上内容覆盖了 React Router 的核心用法,从基础路由配置到动态参数、嵌套路由和权限控制。建议结合实际场景练习(如电商网站的商品列表→详情页跳转、需要登录的个人中心),加深对路由逻辑的理解。如果有具体场景的疑问,可以进一步探讨!

React 状态管理学习知识点(第 32 - 39 天)

第 32 - 33 天:Context API 基本使用

什么是 Context API?

Context API 是 React 内置的状态管理方案,用于解决跨组件数据传递(避免 props 层层传递的“props drilling”问题)。适用于中小型应用的简单状态共享。

核心步骤:创建 → 提供 → 消费

1. 创建上下文(createContext)

使用 createContext 函数创建一个上下文对象,可指定默认值(当组件未被 Provider 包裹时使用)。

// 创建上下文(ThemeContext.js)
import { createContext } from 'react';

// 默认值为 "light"
const ThemeContext = createContext("light");

export default ThemeContext;
2. 提供状态(Provider 组件)

通过 Context 的 Provider 组件包裹子组件树,使用 value 属性提供共享状态,所有子组件(无论层级)都能访问该状态。

// 父组件(App.js)
import ThemeContext from './ThemeContext';
import Child from './Child';

function App() {
  const theme = "dark"; // 共享的状态

  return (
    // 提供主题状态,所有子组件可访问
    <ThemeContext.Provider value={theme}>
      <div>
        <Child />
      </div>
    </ThemeContext.Provider>
  );
}
3. 消费状态(两种方式)
方式 1:Consumer 组件

通过 Context.Consumer 组件的回调函数获取状态,适用于类组件函数组件

// 子组件(Child.js)
import ThemeContext from './ThemeContext';

function Child() {
  return (
    <ThemeContext.Consumer>
      {/* 回调函数的参数即为 Provider 提供的 value */}
      {(theme) => <p>当前主题:{theme}</p>}
    </ThemeContext.Consumer>
  );
}
方式 2:useContext Hook(推荐)

函数组件中使用 useContext Hook 直接获取状态,代码更简洁。

// 子组件(Child.js)
import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function Child() {
  // 直接获取上下文状态
  const theme = useContext(ThemeContext);
  return <p>当前主题:{theme}</p>;
}

跨组件共享示例

多层级组件共享用户信息:

// UserContext.js
import { createContext } from 'react';
export const UserContext = createContext(null);

// 顶层组件
function App() {
  const user = { name: "Alice", age: 25 };
  return (
    <UserContext.Provider value={user}>
      <Parent />
    </UserContext.Provider>
  );
}

// 中间组件(无需传递 props)
function Parent() {
  return <Child />;
}

// 深层子组件(直接获取用户信息)
function Child() {
  const user = useContext(UserContext);
  return <p>用户名:{user.name}</p>;
}

第 34 - 35 天:Context API 状态更新与适用场景

Context 状态更新

Context 本身不提供状态更新机制,需结合 useStateuseReducer 实现。核心思路:在 Provider 中定义修改状态的方法,并通过 value 传递给子组件,子组件调用方法更新状态。

示例:实现主题切换功能

// ThemeContext.js
import { createContext, useState } from 'react';

const ThemeContext = createContext();

// 封装 Provider 组件,集中管理状态和更新方法
export function ThemeProvider({ children }) {
  // 定义状态和更新方法
  const [theme, setTheme] = useState("light");
  const toggleTheme = () => {
    setTheme(prev => prev === "light" ? "dark" : "light");
  };

  // 提供状态和方法(包装成对象)
  const value = {
    theme,
    toggleTheme
  };

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

export default ThemeContext;

子组件使用更新方法:

// 子组件
import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function ThemeToggle() {
  // 获取状态和更新方法
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <div>
      <p>当前主题:{theme}</p>
      <button onClick={toggleTheme}>切换主题</button>
    </div>
  );
}

// 顶层组件使用 ThemeProvider 包裹
function App() {
  return (
    <ThemeProvider>
      <ThemeToggle />
    </ThemeProvider>
  );
}

适用场景与局限性

适用场景
  • 中小型应用的简单状态共享(如主题、用户登录状态)。
  • 状态变化频率低的场景。
  • 跨少量层级组件的数据传递。
局限性
  • 性能问题:当 Context 的 value 变化时,所有消费该 Context 的组件都会强制重渲染,即使不需要该状态的组件也可能被牵连。
  • 复杂状态管理困难:无法像 Redux 那样中间件(如日志、异步处理),不适合管理包含大量状态或复杂状态逻辑的应用。
  • 调试不便:缺乏状态追踪工具,难以定位状态更新问题。

第 36 天:Redux 核心概念

Redux 是独立于 React 的状态管理库,基于“单一数据源”和“单向数据流”设计,适用于复杂应用的状态管理。

核心概念

  1. Store

    • 定义:存储整个应用状态的唯一容器(一个应用只有一个 Store)。
    • 作用:提供方法获取状态(getState())、分发动作(dispatch(action))、监听状态变化(subscribe(listener))。
  2. Action

    • 定义:描述“发生了什么”的普通 JavaScript 对象,必须包含 type 字段(字符串常量,描述动作类型)。
    • 示例:
      // 计数器加 1 的动作
      { type: 'INCREMENT' }
      
      // 带额外数据的动作(如添加商品)
      { type: 'ADD_TODO', payload: '学习 Redux' }
      
    • 注意:Action 本身不修改状态,只描述变化。
  3. Reducer

    • 定义:根据 Action 计算新状态的纯函数(state, action) => newState)。
    • 规则:
      • 不能修改原状态(必须返回新对象/数组)。
      • 无副作用(不调用 API、不修改外部变量)。
      • 相同输入必须返回相同输出。
    • 示例(计数器 Reducer):
      function counterReducer(state = 0, action) {
        switch (action.type) {
          case 'INCREMENT':
            return state + 1; // 返回新状态
          case 'DECREMENT':
            return state - 1;
          default:
            return state; // 默认返回原状态
        }
      }
      
  4. 单向数据流
    Redux 状态更新遵循严格的单向流程:

    1. 组件触发 Action(如用户点击按钮)→
    2. 通过 dispatch 分发 Action →
    3. Reducer 根据 Action 计算新状态 →
    4. Store 更新状态 →
    5. 组件感知状态变化并重新渲染
    

第 37 - 38 天:Redux 使用流程

步骤 1:安装 Redux

npm install redux

步骤 2:创建 Reducer

定义处理状态更新的纯函数,初始化状态并根据 Action 类型返回新状态。

// reducers/counterReducer.js
// 初始状态
const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 }; // 复制原状态,修改 count
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'SET_COUNT':
      return { ...state, count: action.payload }; // 使用 action 携带的数据
    default:
      return state;
  }
}

export default counterReducer;

步骤 3:定义 Action Type 和 Action Creator

  • Action Type:用常量定义动作类型,避免拼写错误。
  • Action Creator:返回 Action 的函数,简化 Action 创建。
// actions/counterActions.js
// Action Type 常量
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const SET_COUNT = 'SET_COUNT';

// Action Creator
export function increment() {
  return { type: INCREMENT };
}

export function decrement() {
  return { type: DECREMENT };
}

export function setCount(value) {
  return { 
    type: SET_COUNT, 
    payload: value // 携带额外数据
  };
}

步骤 4:创建 Store

使用 createStore 函数创建 Store,传入 Reducer。

// store/index.js
import { createStore } from 'redux';
import counterReducer from '../reducers/counterReducer';

// 创建 Store
const store = createStore(counterReducer);

export default store;

步骤 5:使用 Store(获取/更新状态)

  • store.getState():获取当前状态。
  • store.dispatch(action):分发 Action 触发状态更新。
  • store.subscribe(listener):监听状态变化(通常在 React 中由 react-redux 自动处理)。

示例:

import store from './store';
import { increment, setCount } from './actions/counterActions';

// 初始状态
console.log(store.getState()); // { count: 0 }

// 分发 Action 更新状态
store.dispatch(increment());
console.log(store.getState()); // { count: 1 }

store.dispatch(setCount(10));
console.log(store.getState()); // { count: 10 }

// 监听状态变化
store.subscribe(() => {
  console.log('状态更新:', store.getState());
});

第 39 天:react-redux 与 redux-thunk

react-redux:连接 React 与 Redux

react-redux 是官方库,简化 Redux 与 React 组件的连接,核心组件和 Hook 如下:

1. Provider 组件

将 Store 传递给整个 React 应用,使所有组件可访问 Store。

// index.js
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
2. 组件连接方式
方式 1:useSelector 与 useDispatch(推荐,函数组件)
  • useSelector(selector):从 Store 中提取状态(类似 mapStateToProps)。
  • useDispatch():获取 dispatch 方法,用于分发 Action。
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions/counterActions';

function Counter() {
  // 提取 count 状态
  const count = useSelector(state => state.count);
  // 获取 dispatch 方法
  const dispatch = useDispatch();

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => dispatch(increment())}>1</button>
      <button onClick={() => dispatch(decrement())}>1</button>
    </div>
  );
}
方式 2:connect 高阶组件(支持类组件)

connect(mapStateToProps, mapDispatchToProps)(Component):将状态和 dispatch 映射到组件 props。

import { connect } from 'react-redux';
import { increment } from './actions/counterActions';

function Counter({ count, increment }) {
  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={increment}>1</button>
    </div>
  );
}

// 映射状态到 props
const mapStateToProps = (state) => ({
  count: state.count
});

// 映射 dispatch 到 props
const mapDispatchToProps = {
  increment // 等价于 () => dispatch(increment())
};

// 连接组件
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

redux-thunk:处理异步 Action

Redux 默认只支持同步 Action,redux-thunk 中间件允许 Action Creator 返回函数(而非对象),用于处理异步操作(如 API 请求)。

步骤 1:安装并配置
npm install redux-thunk

配置中间件:

// store/index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import counterReducer from '../reducers/counterReducer';

// 应用 thunk 中间件
const store = createStore(counterReducer, applyMiddleware(thunk));

export default store;
步骤 2:创建异步 Action Creator

返回的函数接收 dispatchgetState 作为参数,完成异步操作后分发同步 Action。

// actions/dataActions.js
// 异步获取数据的 Action Creator
export function fetchData() {
  // 返回函数(由 thunk 处理)
  return async (dispatch) => {
    try {
      // 1. 分发“开始请求”的 Action
      dispatch({ type: 'FETCH_START' });
      
      // 2. 异步请求(如 API 调用)
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      
      // 3. 请求成功,分发“获取数据”的 Action
      dispatch({ type: 'FETCH_SUCCESS', payload: data });
    } catch (error) {
      // 4. 请求失败,分发“请求失败”的 Action
      dispatch({ type: 'FETCH_ERROR', payload: error.message });
    }
  };
}
步骤 3:组件中使用异步 Action
import { useDispatch, useSelector } from 'react-redux';
import { fetchData } from './actions/dataActions';

function DataComponent() {
  const { data, loading, error } = useSelector(state => state);
  const dispatch = useDispatch();

  return (
    <div>
      <button onClick={() => dispatch(fetchData())}>获取数据</button>
      {loading && <p>加载中...</p>}
      {error && <p>错误:{error}</p>}
      {data && <pre>{JSON.stringify(data)}</pre>}
    </div>
  );
}

总结

  • Context API:适合简单状态共享,实现简单但性能有限。
  • Redux:适合复杂应用,状态管理规范,可扩展性强(结合中间件)。
  • react-redux:简化 Redux 与 React 的集成,推荐使用 useSelectoruseDispatch
  • redux-thunk:解决 Redux 异步处理问题,允许 Action Creator 返回函数。

根据应用复杂度选择合适的状态管理方案,小型应用优先考虑 Context API + useReducer,大型应用推荐 Redux 生态。

有用的链接

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值