React基础学习-Day02

1.受控表单绑定

在 React 中,受控表单(controlled form)是一种通过 React 组件状态(state)来管理表单元素值的方式。使用受控表单,可以将表单元素的值与 React 组件的状态保持同步,从而实现对表单数据的完全控制和管理。

如何创建受控表单

1. 输入框(Input)

使用 <input> 元素作为例子,展示如何创建一个受控输入框:

import React, { useState } from 'react';

const ControlledFormExample = () => {
  // 定义一个状态变量来存储输入框的值
  const [inputValue, setInputValue] = useState('');

  // 处理输入框值变化的函数
  const handleInputChange = (event) => {
    setInputValue(event.target.value);  // 更新状态变量的值
  };

  // 提交表单时处理函数
  const handleSubmit = (event) => {
    event.preventDefault();  // 阻止表单默认提交行为
    alert(`Submitted value: ${inputValue}`);  // 弹出输入框的当前值
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Input:
        <input
          type="text"
          value={inputValue}  // 将输入框的值与状态变量绑定
          onChange={handleInputChange}  // 处理输入框值变化的函数
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default ControlledFormExample;
2. 复选框(Checkbox)

对于复选框,需要注意初始状态和处理函数的变化:

import React, { useState } from 'react';

const CheckboxExample = () => {
  // 定义一个状态变量来存储复选框的选中状态
  const [isChecked, setIsChecked] = useState(false);

  // 处理复选框状态变化的函数
  const handleCheckboxChange = (event) => {
    setIsChecked(event.target.checked);  // 更新状态变量的值
  };

  // 提交表单时处理函数
  const handleSubmit = (event) => {
    event.preventDefault();  // 阻止表单默认提交行为
    alert(`Checkbox checked: ${isChecked}`);  // 弹出复选框的当前选中状态
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Checkbox:
        <input
          type="checkbox"
          checked={isChecked}  // 将复选框的选中状态与状态变量绑定
          onChange={handleCheckboxChange}  // 处理复选框状态变化的函数
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default CheckboxExample;
3. 下拉菜单(Select)

对于 <select> 元素,需要注意选项列表的处理和初始选中值:

import React, { useState } from 'react';

const SelectExample = () => {
  // 定义一个状态变量来存储选择框的当前选中值
  const [selectedValue, setSelectedValue] = useState('option1');

  // 处理选择框选项变化的函数
  const handleSelectChange = (event) => {
    setSelectedValue(event.target.value);  // 更新状态变量的值
  };

  // 提交表单时处理函数
  const handleSubmit = (event) => {
    event.preventDefault();  // 阻止表单默认提交行为
    alert(`Selected value: ${selectedValue}`);  // 弹出选择框的当前选中值
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Select:
        <select value={selectedValue} onChange={handleSelectChange}>
          <option value="option1">Option 1</option>
          <option value="option2">Option 2</option>
          <option value="option3">Option 3</option>
        </select>
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default SelectExample;

特点和优势

  • 完全控制: 受控表单使得所有表单元素的值都由 React 组件状态管理,可以通过状态更新函数精确控制和验证输入。
  • 一致性和可预测性: 因为状态直接驱动表单元素的值,所以可以确保表单的行为一致和可预测。
  • 验证和处理: 可以轻松实现输入验证、条件渲染和复杂的表单逻辑,例如禁用按钮直到所有必填字段都填写完毕。

通过使用受控表单,React 组件能够更加灵活和可控地处理用户输入,提高了应用的可维护性和用户体验。

2.React中如何获取DOM

在 React 中获取 DOM 元素通常是通过 Refs(引用)来实现的。Refs 提供了一种直接访问在 render 方法中创建的 DOM 节点或 React 元素的方式。这种访问方式是为了避免直接操作 DOM,符合 React 的声明式和组件化的设计理念。

使用 Refs 获取 DOM

1. 创建 Refs

在函数式组件中,可以使用 useRef 钩子来创建 Ref 对象:

import React, { useRef } from 'react';

const MyComponent = () => {
  // 创建一个 Ref 对象
  const myRef = useRef(null);

  // 在 useEffect 中操作 Ref
  useEffect(() => {
    console.log(myRef.current);  // 输出 Ref 对象的当前值(关联的 DOM 节点)
  }, []);

  return (
    <div ref={myRef}>
      {/* 这里是组件内容 */}
    </div>
  );
};

export default MyComponent;
2. 访问 DOM 节点

通过 ref.current 属性可以访问到 Ref 对象关联的 DOM 节点。需要注意的是,Ref 对象在组件的整个生命周期中保持不变,但 ref.current 的值会随着组件的渲染和卸载而变化。

3. 类组件中的 Refs

在类组件中,Refs 的创建和访问稍有不同:

import React, { Component } from 'react';

class MyComponent extends Component {
  constructor(props) {
    super(props);
    // 创建一个 Ref 对象
    this.myRef = React.createRef();
  }

  componentDidMount() {
    console.log(this.myRef.current);  // 输出 Ref 对象的当前值(关联的 DOM 节点)
  }

  render() {
    return (
      <div ref={this.myRef}>
        {/* 这里是组件内容 */}
      </div>
    );
  }
}

export default MyComponent;

在类组件中,通过 React.createRef() 方法创建 Ref 对象,并在 componentDidMount 生命周期中访问 this.myRef.current 获取 DOM 节点。

使用场景

  • 直接操作 DOM: 当需要在 React 中进行 DOM 操作时,例如测量元素尺寸、动画操作等,可以使用 Refs 来获取并操作 DOM 元素。
  • 集成第三方库: 当需要与不支持 React 的第三方库(如 D3.js、Chart.js 等)集成时,Refs 提供了一种访问和控制 DOM 的方式。
  • 焦点控制: 控制输入框、模态框等组件的焦点状态。

注意事项

  • 避免滥用 Refs: 在大多数情况下,应该优先考虑使用 React 的数据驱动和状态管理方式,而不是直接操作 DOM。
  • Refs 和函数式组件: 在函数式组件中,确保使用 useRef 钩子来创建 Ref 对象,而不是直接操作 DOM。

通过使用 Refs,在 React 中可以安全地获取和操作 DOM 元素,同时保持 React 的组件化和声明式编程模型的优势。

3.组件间通信

在 React 中,组件间通信是非常常见和重要的一部分,特别是在大型应用程序中,不同组件之间需要相互传递数据、状态或触发特定行为。以下是几种主要的 React 组件间通信方式:

1. Props 属性传递

Props(属性)是 React 中组件间通信最基础和常见的方式。通过将数据或函数作为属性传递给子组件,可以实现父组件向子组件的数据传递。

父组件传递数据给子组件:

// ParentComponent.jsx
import React from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const data = 'Hello from parent';

  return (
    <div>
      <ChildComponent message={data} />
    </div>
  );
};

export default ParentComponent;

子组件接收并使用 Props 数据:

// ChildComponent.jsx
import React from 'react';

const ChildComponent = (props) => {
  return (
    <div>
      <p>{props.message}</p>
    </div>
  );
};

export default ChildComponent;

2. Callback 函数传递

通过将回调函数作为 Props 传递给子组件,子组件可以调用该函数并将数据或事件传递回父组件。

父组件定义回调函数并传递给子组件:

// ParentComponent.jsx
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [message, setMessage] = useState('');

  const handleCallback = (data) => {
    setMessage(data);
  };

  return (
    <div>
      <p>Message from child: {message}</p>
      <ChildComponent callback={handleCallback} />
    </div>
  );
};

export default ParentComponent;

子组件调用回调函数并传递数据:

// ChildComponent.jsx
import React from 'react';

const ChildComponent = (props) => {
  const sendDataToParent = () => {
    const data = 'Hello from child';
    props.callback(data);
  };

  return (
    <div>
      <button onClick={sendDataToParent}>Send Data</button>
    </div>
  );
};

export default ChildComponent;

3. Context API

Context API 提供了一种在组件树中传递数据的方法,避免了通过 Props 层层传递数据的繁琐过程。适合在多层级嵌套的组件中使用,允许跨越组件层级直接传递数据。

创建 Context 对象并提供数据:

// MyContext.js
import React from 'react';

const MyContext = React.createContext();

export const MyProvider = MyContext.Provider;
export const MyConsumer = MyContext.Consumer;

使用 Context 提供数据并在子组件中消费:

// ParentComponent.jsx
import React from 'react';
import { MyProvider } from './MyContext';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const data = 'Hello from context';

  return (
    <MyProvider value={data}>
      <ChildComponent />
    </MyProvider>
  );
};

export default ParentComponent;

在子组件中消费 Context 中的数据:

// ChildComponent.jsx
import React, { useContext } from 'react';
import { MyContext } from './MyContext';

const ChildComponent = () => {
  const data = useContext(MyContext);

  return (
    <div>
      <p>{data}</p>
    </div>
  );
};

export default ChildComponent;

4. Redux 或其他状态管理工具

对于大型应用程序中复杂的状态管理需求,可以使用 Redux 或其他状态管理工具(如 MobX)来集中管理应用程序的状态,并通过全局状态来实现组件间通信。这种方式适合需要多个组件共享状态或频繁更新状态的场景。

选择合适的通信方式

  • Props 传递:简单、直观,适用于父子组件间的数据传递。
  • Callback 函数:父组件可以向子组件传递函数来处理子组件中的事件,适合于子组件向父组件传递数据或事件。
  • Context API:适用于跨组件层级传递数据,避免 Props 层层传递的麻烦。
  • Redux 或其他状态管理工具:适用于需要全局状态管理或复杂的状态更新和同步需求的应用程序。

根据具体的场景和需求,选择适合的组件间通信方式是提高 React 应用开发效率和可维护性的关键。

4.useEffect

useEffect 是 React Hooks 中非常重要的一个钩子函数,用于在函数组件中执行副作用操作。副作用操作通常包括订阅数据、手动操作 DOM 和执行数据获取等。

基本用法

import React, { useEffect, useState } from 'react';

const MyComponent = () => {
  // 定义一个状态来存储数据
  const [data, setData] = useState(null);

  // useEffect 第一个参数是一个函数,第二个参数是一个数组
  useEffect(() => {
    // 在组件加载后和每次更新后执行

    // 这里可以进行数据获取、订阅操作、手动 DOM 更新等异步操作
    fetchData();

    // 清理函数,可选,用于清理副作用
    return () => {
      cleanupFunction();
    };
  }, []); // 空数组意味着只在组件加载后执行一次

  const fetchData = async () => {
    // 异步操作示例,获取数据
    const result = await fetch('https://api.example.com/data');
    const data = await result.json();
    setData(data);
  };

  const cleanupFunction = () => {
    // 清理函数,比如清除订阅或取消定时器
    // 在组件卸载时执行
  };

  return (
    <div>
      {data ? (
        <p>Data loaded: {JSON.stringify(data)}</p>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
};

export default MyComponent;

useEffect 的工作原理

  • 第一个参数(effect):一个函数,包含需要执行的副作用操作,可以是数据获取、订阅操作、DOM 操作等异步任务。
  • 第二个参数(dependencies 数组):可选参数,指定 effect 的依赖项。当依赖项发生变化时,effect 就会重新执行。如果不传递该参数,每次组件更新时都会执行 effect。

依赖项的作用

  • 空数组 []:effect 仅在组件加载和卸载时执行,相当于 componentDidMountcomponentWillUnmount 生命周期的结合体。
  • 包含状态或属性的数组:effect 会在依赖项中的状态或属性发生变化时执行。比如 [count] 会在 count 状态更新时执行 effect。
  • 不传递第二个参数:effect 在每次组件更新时都会执行,相当于 componentDidUpdate 生命周期。

清理副作用

在 effect 函数中可以返回一个清理函数,用于清理副作用,比如取消订阅、清除定时器等。这样可以避免内存泄漏和无效的异步操作。

useEffect(() => {
  const subscription = subscribeToData();
  
  return () => {
    subscription.unsubscribe(); // 清理订阅
  };
}, [dependency]);

总结

useEffect 是 React Hooks 提供的强大工具,用于处理函数组件中的副作用操作。通过指定依赖项数组,可以精确控制 effect 的执行时机,避免不必要的重复执行,提高组件性能和可维护性。

5.自定义Hook

自定义 Hook 是 React 中的一个强大功能,它允许你将组件逻辑提取到可复用的函数中。自定义 Hook 的命名通常以 use 开头。

创建一个自定义 Hook

我们将创建一个简单的自定义 Hook useCounter,它管理一个计数器的状态并提供增加和减少计数器的方法。

Step 1: 定义自定义 Hook
import { useState } from 'react';

const useCounter = (initialValue = 0) => {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  return { count, increment, decrement };
};

export default useCounter;
Step 2: 使用自定义 Hook

你可以在任意函数组件中使用这个自定义 Hook:

import React from 'react';
import useCounter from './useCounter';

const CounterComponent = () => {
  const { count, increment, decrement } = useCounter(10);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default CounterComponent;

自定义 Hook 的优势

  1. 逻辑复用:将重复使用的逻辑提取到自定义 Hook 中,使代码更加模块化和可复用。
  2. 更清晰的组件结构:通过将复杂逻辑抽离到自定义 Hook 中,组件代码变得更简洁和易读。
  3. 状态隔离:每次调用自定义 Hook 都会生成独立的状态,互不影响。

复杂的自定义 Hook 示例

创建一个自定义 Hook useFetch 来处理数据获取:

Step 1: 定义 useFetch Hook
import { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const data = await response.json();
        setData(data);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;
Step 2: 使用 useFetch Hook
import React from 'react';
import useFetch from './useFetch';

const DataComponent = () => {
  const { data, loading, error } = useFetch('https://api.example.com/data');

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default DataComponent;

总结

自定义 Hook 允许你将组件逻辑提取到可复用的函数中,提高代码的可读性和维护性。通过自定义 Hook,可以更方便地复用逻辑、管理复杂的状态和副作用,从而简化组件代码。