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 仅在组件加载和卸载时执行,相当于componentDidMount
和componentWillUnmount
生命周期的结合体。 - 包含状态或属性的数组: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 的优势
- 逻辑复用:将重复使用的逻辑提取到自定义 Hook 中,使代码更加模块化和可复用。
- 更清晰的组件结构:通过将复杂逻辑抽离到自定义 Hook 中,组件代码变得更简洁和易读。
- 状态隔离:每次调用自定义 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,可以更方便地复用逻辑、管理复杂的状态和副作用,从而简化组件代码。