useState
状态管理useState
是 React 中的一个基础 Hook,允许你在不使用 class 组件的情况下管理组件状态。
你可以直接传递状态的初始值给 useState
:
const [name, setName] = useState("John");
当初始化状态代价较大时,你可以传递一个函数:
const [state, setState] = useState(() => {
const initialState = calculateInitialState(); // 一些复杂的操作
return initialState;
});
useState
返回一个数组,其中包括当前状态值和一个更新状态的函数。
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
function UserProfile() {
const [profile, setProfile] = useState({ name: "John", age: 30 });
const updateName = name => {
setProfile(prevProfile => ({ ...prevProfile, name }));
};
return (
<div>
<p>Name: {profile.name}</p>
<p>Age: {profile.age}</p>
<button onClick={() => updateName("Doe")}>Update Name</button>
</div>
);
}
useState
你可以在一个组件中使用多个 useState
来管理不同的状态片段:
function MultiCounter() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div>
<p>Counter 1: {count1}</p>
<button onClick={() => setCount1(count1 + 1)}>Increment Counter 1</button>
<p>Counter 2: {count2}</p>
<button onClick={() => setCount2(count2 + 1)}>Increment Counter 2</button>
</div>
);
}
调用位置:在 React 函数的最顶层调用 useState
。
依赖之前的状态更新:使用函数式更新,如:setCount(prevCount => prevCount + 1)
。
不会自动合并对象:手动合并对象,如:setState(prevState => ({ ...prevState, key: 'value' }))
。
useEffect
副作用操作React 函数组件的副作用利器
在编程中,副作用是指代码对外部世界产生的影响。比如说,你想在用户点击按钮后改变网页的标题。这个改变就是一个副作用。
useEffect
?React 组件通常用于渲染 UI,但有时你还需要执行一些额外的操作,如网络请求、操作 DOM 或订阅事件等。这些操作就是所谓的副作用,而 useEffect
就是用来处理这些副作用的工具。
useEffect
?useEffect
的使用很简单。它接受两个参数:一个副作用函数和一个依赖数组。
假设你想在组件挂载后改变网页标题。你可以这样做:
useEffect(() => {
document.title = "新的标题";
}, []); // 空数组表示只在组件挂载后执行一次
如果你想根据用户 ID 获取用户数据,你可以这样做:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`)
.then(response => response.json())
.then(setUser);
}, [userId]); // 当 userId 改变时,重新获取数据
}
清理副作用:如果你的副作用涉及需要清理的操作(例如取消订阅事件),你可以在副作用函数中返回一个清理函数。
useEffect(() => {
const timerId = setInterval(() => {
console.log("Tick");
}, 1000);
return () => {
clearInterval(timerId); // 清理定时器
};
}, []);
避免无限循环:不小心使用 useEffect
可能导致无限循环。务必注意你的依赖数组,确保副作用按预期执行。
useMemo
记忆计算值useMemo
钩子用于在组件渲染期间记住复杂计算的结果。这可以提高性能,尤其是当有大量计算和重新计算时。
useMemo
?当组件重新渲染时,可能会涉及到一些复杂的计算。如果这些计算的依赖项没有改变,那么重新进行这些计算就是浪费。useMemo
允许你记住这些计算的结果,只有当依赖项改变时才重新计算。
useMemo
?useMemo
接受两个参数:一个函数和一个依赖项数组。它会返回该函数的返回值。
下面的示例展示了如何使用 useMemo
来过滤大于 10 的数字。只有当数字数组改变时,才会重新计算过滤的结果。
import React, { useMemo, useState } from "react";
interface Props {
items: number[];
}
const ExpensiveComponent: React.FC<Props> = ({ items }) => {
const filteredItems = useMemo(() => items.filter(item => item > 10), [items]);
return (
<div>
<h3>Filtered Items (Greater than 10):</h3>
{filteredItems.map(item => (
<div key={item}>{item}</div>
))}
</div>
);
};
const App: React.FC = () => {
const [items, setItems] = useState<number[]>([5, 12, 8, 20, 33]);
const addItem = () => setItems([...items, Math.floor(Math.random() * 40)]);
return (
<div>
<button onClick={addItem}>Add Item</button>
<h3>All Items:</h3>
{items.map(item => (
<div key={item}>{item}</div>
))}
<ExpensiveComponent items={items} />
</div>
);
};
export default App;
useMemo
作为性能优化的首要工具。除非你确实遇到性能问题,并且确定了 useMemo
可以解决问题,否则不要过早优化。useMemo
的函数内部执行有副作用的操作。使用 useEffect
处理副作用。useMemo
是一个非常强大的工具,可以提高组件的性能,尤其是在复杂的计算和频繁的重新渲染中。但是,也要谨慎使用它,确保只在确实需要时使用它,并始终确保你的代码的正确性和可维护性。
useCallback
:记忆化函数useCallback
?useCallback
用于在组件的连续渲染之间记忆化(或缓存)函数。它帮助避免因父组件重新渲染而导致的不必要的子组件渲染。
useCallback
接受两个参数:一个函数和一个依赖数组。只有当依赖数组中的值发生变化时,函数才会被重新创建。
让我们看一个完整的示例,展示如何使用 useCallback
来优化组件的渲染。
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onClick }) => {
console.log("ChildComponent re-rendered!");
return <button onClick={onClick}>Click Me!</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback 记忆化 handleClick 函数
const handleClick = useCallback(() => {
console.log("Button was clicked");
}, []); // 空依赖数组,表示该函数不会重新创建
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>Increment Counter</button>
{/* 传递记忆化的 handleClick 到 ChildComponent */}
<ChildComponent onClick={handleClick} />
</div>
);
}
export default ParentComponent;
在上面的示例中,ChildComponent
接受一个 onClick
属性,并且使用 React.memo
进行了优化,所以只有当 onClick
属性发生变化时才会重新渲染。由于 handleClick
使用 useCallback
被记忆化了,因此即使父组件重新渲染,handleClick
函数也不会更改,从而避免了子组件的不必要渲染。
useCallback
。如果一个函数没有传递给子组件或其他记忆化操作,使用 useCallback
可能会带来更多的复杂性和性能开销。useCallback
是一个强大的优化工具,可以在适当的情况下提高 React 组件的性能。通过使用此 Hook,你可以控制函数的重新创建,从而避免因父组件重新渲染而导致的不必要的子组件渲染。
该示例展示了如何在实际组件中使用 useCallback
进行优化,希望这有助于你更深入地理解这个 Hook 的用途和工作方式。如果你有任何问题或需要进一步的解释,请随时提问!
useCallback
用于记忆化函数,而 useMemo
用于记忆化计算值。useCallback
返回一个记忆化的函数;useMemo
返回一个记忆化的计算结果。useCallback
。useMemo
useRef
访问 Ref用于访问和操作 DOM 元素及保持可变的引用值
useRef
?useRef
Hook 用于访问和操作 DOM 元素,并在组件的整个生命周期中保持不变的引用。除了用于直接操作 DOM,useRef
还可用于在组件的不同渲染之间保留可变的引用值,而不触发重新渲染。
useRef
?要使用 useRef
,你首先需要调用它,并将初始值作为参数传递(如果有)。这将返回一个 ref
对象,该对象具有一个名为 current
的属性,该属性将引用传递给 useRef
的初始值或 DOM 元素。
useRef
访问 DOM 元素你可以使用 useRef
直接访问和操作 DOM 元素。下面是一个示例,显示了如何使用 useRef
控制输入元素的焦点。
import React, { useRef } from "react";
function TextInput() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus(); // 使用 ref 对象的 current 属性访问输入元素
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={handleFocus}>Focus the input</button>
</div>
);
}
在上述示例中,我们创建了一个 ref
对象并将其分配给输入元素。然后,我们可以使用该引用在按钮点击时将焦点设置到输入元素上。
useRef
保留可变的引用值除了用于访问 DOM 元素外,useRef
还可以用于在组件的连续渲染之间保留可变的引用值,而不触发重新渲染。
import React, { useRef, useEffect } from "react";
function Timer() {
const countRef = useRef(0);
useEffect(() => {
const intervalId = setInterval(() => {
countRef.current += 1; // 更新 ref 的 current 值
console.log("Timer:", countRef.current);
}, 1000);
return () => clearInterval(intervalId); // 清除计时器
}, []); // 空依赖数组表示该效果只在挂载和卸载时运行
return <div>Check the console to see the timer count!</div>;
}
在这个示例中,我们使用 useRef
来保持计时器的计数值。因为 ref
对象的更改不会触发组件的重新渲染,所以它是一个非常有用的工具,用于在重新渲染之间保留值。
useRef
是一个多功能的 React Hook,可以用于多种用途:
current
属性直接访问和操作 DOM 元素。在 React 中,你可以使用引用(ref)与自定义组件进行交互,访问组件的实例。这是一个非常有用的特性,可以用于获取组件内部的信息,调用组件内部的方法,或者与组件进行更复杂的交互。
forwardRef
与自定义组件对于自定义组件,你通常需要使用 forwardRef
来转发 ref。下面是一个简单的示例,显示如何使用 forwardRef
创建自定义组件,并从父组件中访问该组件的 DOM 元素:
import React, { useRef, forwardRef } from "react";
const CustomComponent = forwardRef((props, ref) => {
return <div ref={ref}>Custom Component</div>;
});
function App() {
const customComponentRef = useRef(null);
const handleClick = () => {
customComponentRef.current.style.backgroundColor = "lightblue";
};
return (
<div>
<CustomComponent ref={customComponentRef} />
<button onClick={handleClick}>Change Background Color</button>
</div>
);
}
export default App;
useImperativeHandle
暴露自定义实例属性有时,你可能希望自定义组件能够暴露特定的实例方法或属性给父组件。在这种情况下,你可以使用 useImperativeHandle
进行更精细的控制。下面的示例显示了如何使用 useImperativeHandle
暴露组件的特定方法:
import React, { useRef, forwardRef, useImperativeHandle } from "react";
const CustomComponent = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
sayHello: () => {
alert("Hello from CustomComponent!");
},
}));
return <div>Custom Component</div>;
});
function App() {
const customComponentRef = useRef(null);
const handleClick = () => {
customComponentRef.current.sayHello(); // 调用自定义组件的方法
};
return (
<div>
<CustomComponent ref={customComponentRef} />
<button onClick={handleClick}>Say Hello</button>
</div>
);
}
export default App;
与自定义组件一起使用 ref 可以实现许多强大的功能,从访问组件内部的 DOM 元素,到调用组件的特定实例方法。通过使用 forwardRef
和 useImperativeHandle
,你可以更灵活地控制自定义组件的行为,并与之进行更复杂的交互。
请注意,直接操作 DOM 和组件实例通常应该作为最后的手段,因为它可能会导致代码难以理解和维护。在可能的情况下,尽量使用正常的 React 数据流来管理组件的状态和行为。
useContext
访问上下文useContext
钩子是 React 中一个非常强大的工具,用于在组件树中跨层级共享状态。它消除了通过多层组件手动传递 props 的需要,使得状态管理变得更加清晰和高效。
useContext
?在复杂的 React 应用程序中,状态需要在多个组件之间共享。传统方法是将状态作为 props 从顶层组件一层一层传递下去,但这会导致代码混乱且难以维护。useContext
允许我们跨越组件层级直接共享状态,无需手动传递。
useContext
?创建上下文: 使用 React 的 createContext
方法创建一个上下文。
提供上下文: 使用 Context.Provider
组件在组件树中提供上下文的值。
消费上下文: 在任何子组件中使用 useContext
钩子访问上下文的值。
下面的示例展示了如何使用 useContext
来共享主题设置。
import React, { useContext, useState } from "react";
// 创建上下文
const ThemeContext = React.createContext<{ theme: string; toggleTheme: () => void } | undefined>(undefined);
const App: React.FC = () => {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light");
};
// 提供上下文
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<Header />
<MainContent />
</ThemeContext.Provider>
);
};
const Header: React.FC = () => {
// 消费上下文
const themeContext = useContext(ThemeContext);
if (!themeContext) {
return <h1>Error: Theme not found</h1>;
}
return (
<div>
<h1>{themeContext.theme} theme is active</h1>
<button onClick={themeContext.toggleTheme}>Toggle Theme</button>
</div>
);
};
const MainContent: React.FC = () => {
// ...
return <div>Main Content</div>;
};
export default App;
useContext
之前已经在组件树中提供了上下文。useContext
钩子是一个强大的工具,用于组件之间的跨层级状态共享。通过消除手动传递 props 的需要,它可以使你的代码更加清晰、简洁,同时也提高了代码的可维护性。
useReducer
复杂状态逻辑useReducer
是 React 中用于处理组件状态逻辑的钩子,尤其适用于更复杂或包括多个子值的状态。它的工作原理类似于 Redux,但是更加精简,不需要引入额外的库。
useReducer
?当状态逻辑复杂或者下一状态依赖于之前的状态时,useReducer
非常有用。相较于 useState
,它提供了更可预测的状态更新方式,并且更容易测试和维护。
useReducer
?useReducer
接收两个参数:一个 reducer 函数和初始状态,返回当前状态和一个 dispatch 函数。
Reducer 函数: 这个函数接收两个参数:当前的状态和一个动作。根据动作类型,它返回一个新的状态。
初始状态: 初始状态是 reducer 的第一个参数的初始值。
Dispatch 函数: 这个函数用于分派动作,触发 reducer 函数,并更新状态。
下面的示例演示了如何使用 useReducer
来管理一个计数器的状态。
import React, { useReducer } from "react";
// 定义动作类型
type Action = { type: "increment" } | { type: "decrement" };
// 定义reducer函数
const counterReducer = (state: number, action: Action) => {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
return state;
}
};
const Counter: React.FC = () => {
// 使用useReducer
const [state, dispatch] = useReducer(counterReducer, 0);
return (
<div>
<h1>Count: {state}</h1>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
</div>
);
};
export default Counter;
useReducer
钩子提供了一种更灵活、可维护的方式来处理复杂的状态逻辑。通过结合纯净的 reducer 函数和动作分派,它为开发人员提供了对组件内部状态更细粒度的控制。对于需要管理复杂状态或者希望与 Redux 保持一致的项目,useReducer
是一个极好的选择。
useImperativeHandle
自定义 ref 暴露useImperativeHandle
是一种特殊的钩子,允许你在父组件中直接操作子组件中的某些实例方法。通常来说,在 React 中,组件应该遵循数据自上而下的流动,并通过属性和状态进行通信。但有时,你可能需要在父组件中直接调用子组件的某些方法。这就是 useImperativeHandle
发挥作用的地方。
这个钩子不常用,因为它打破了 React 的一些核心原则,但在某些特定场景下可能很有用。
useImperativeHandle
接受三个参数:
useEffect
或 useMemo
等钩子。如果提供了此参数,则只有当依赖项更改时,才会更新实例方法。假设你有一个 TextInput
组件,并且你想暴露一个方法来清除输入。
import React, { useImperativeHandle, forwardRef, useRef } from "react";
type TextInputHandles = {
clear: () => void;
};
const TextInput = forwardRef<TextInputHandles, {}>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
clear: () => {
if (inputRef.current) {
inputRef.current.value = "";
}
},
}));
return <input type="text" ref={inputRef} />;
});
const ParentComponent = () => {
const inputRef = useRef<TextInputHandles>(null);
const clearInput = () => {
if (inputRef.current) {
inputRef.current.clear();
}
};
return (
<div>
<TextInput ref={inputRef} />
<button onClick={clearInput}>Clear Input</button>
</div>
);
};
export default ParentComponent;
useImperativeHandle
,因为它可能导致代码更难理解和维护。forwardRef
配合使用: 通常,你需要将子组件与 forwardRef
一起使用,以将 ref 传递给子组件。虽然 useImperativeHandle
不是常用的钩子,但在需要在父组件中直接操作子组件的特定场景下,它可能是必要的。通过允许你精确地控制父组件可以访问的子组件方法,它提供了一种强大但易于滥用的工具。在使用它时要小心,并确保这确实是解决问题的最佳方式。