constructor
进行状态初始化。UNSAFE_componentWillMount
、UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
被认为是不安全的,应避免使用。UNSAFE_componentWillMount
中的逻辑,如异步请求,移至componentDidMount
。getDerivedStateFromProps
替代UNSAFE_componentWillReceiveProps
以处理传入的新props。getSnapshotBeforeUpdate
和componentDidUpdate
可以组合使用,替代UNSAFE_componentWillUpdate
的功能。render
应为纯函数,不应产生副作用。render
中调用 setState
或绑定原生事件,以免引发重复渲染或死循环。componentDidMount
中创建的资源(如定时器、事件监听器)应在 componentWillUnmount
中进行清理,防止内存泄漏。getDerivedStateFromProps
存在的唯一目的是使组件能够因为 props 的变化而更新其内部状态import React from "react";
import ReactDOM from "react-dom/client";
function loadMyAsyncData(id) {
let cancel;
let asyncRequest = new Promise((resolve, reject) => {
cancel = reject;
setTimeout(() => {
resolve(`Loaded data for id: ${id}`);
}, 1000);
});
asyncRequest.cancel = cancel;
return asyncRequest;
}
class User extends React.Component {
state = {
externalData: null,
prevId: null,
};
static getDerivedStateFromProps(props, state) {
if (props.id !== state.prevId) {
return {
externalData: null,
prevId: props.id,
};
}
return null;
}
componentDidMount() {
this._loadAsyncData(this.props.id);
}
componentDidUpdate(prevProps, prevState) {
if (
this.state.externalData === null &&
this.state.prevId !== prevState.prevId
) {
this._loadAsyncData(this.props.id);
}
}
componentWillUnmount() {
if (this.asyncRequest) {
this.asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
return <div>Loading...</div>;
} else {
return <div>{this.state.externalData}</div>;
}
}
_loadAsyncData(id) {
if (this.asyncRequest) {
this.asyncRequest.cancel();
}
this.asyncRequest = loadMyAsyncData(id).then((externalData) => {
this.asyncRequest = null;
this.setState({ externalData });
});
}
}
class App extends React.Component {
state = { id: "1" };
handleIdChange = (event) => {
this.setState({ id: event.target.value });
};
render() {
return (
<div>
<User id={this.state.id} />
<input
type="text"
value={this.state.id}
onChange={this.handleIdChange}
/>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
一个常见的误解是,getDerivedStateFromProps
和 componentWillReceiveProps
只在 props “改变”时被调用。
这些生命周期函数在父组件重新渲染时都会被调用,无论 props 是否与之前“不同”。因此,使用这些生命周期函数无条件覆盖 state 一直是不安全的。这样做会导致 state 更新丢失。
import React, { Component } from "react";
import ReactDOM from "react-dom/client";
class EmailInput extends Component {
state = {
email: this.props.email,
};
static getDerivedStateFromProps(nextProps) {
return { email: nextProps.email };
}
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
handleChange = (event) => {
this.setState({
email: event.target.value,
});
};
}
class App extends Component {
state = {
email: "123456@qq.com",
};
render() {
return (
<div>
<EmailInput email={this.state.email} />
{}
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
我们可以通过仅在props.email变化时更新状态来避免意外地擦除状态:
我们刚刚做了一个很大的改进。现在我们的组件只会在属性实际发生变化时才擦除我们所输入的内容。
import React, { Component } from "react";
import ReactDOM from "react-dom/client";
class EmailInput extends Component {
state = {
email: this.props.email,
};
static getDerivedStateFromProps(nextProps,prevState) {
+ if (nextProps.email !== prevState.email) {
+ return { email: nextProps.email };
+ }
+ return null;
}
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
handleChange = (event) => {
this.setState({
email: event.target.value,
});
};
}
class App extends Component {
state = {
email: "123456@qq.com",
};
render() {
return (
<div>
<EmailInput email={this.state.email} />
{}
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
这里仍然存在一个微妙的问题。想象一下一个使用上述输入组件的密码管理应用。当在两个使用相同电子邮件的账户详情之间导航时,输入框将无法重置。这是因为传递给组件的prop值对于这两个账户来说是一样的!这对用户来说会是一个意外,因为对一个账户未保存的更改看起来会影响到其他偶然使用相同电子邮件的账户。
import React, { Component } from "react";
import ReactDOM from "react-dom/client";
class EmailInput extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: props.email
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.email !== prevState.inputValue) {
return { inputValue: nextProps.email };
}
return null;
}
handleChange = (event) => {
this.setState({ inputValue: event.target.value });
}
render() {
return (
<input
value={this.state.inputValue}
onChange={this.handleChange}
placeholder="Email"
/>
);
}
}
const PasswordManagerApp = () => {
const [currentAccount, setCurrentAccount] = React.useState({ id: 1, email: 'user@example.com' });
const switchAccount = () => {
setCurrentAccount({ id: currentAccount.id === 1 ? 2 : 1, email: 'user@example.com' });
};
return (
<div>
<h1>Password Manager</h1>
<button onClick={switchAccount}>Switch Account</button>
<EmailInput email={currentAccount.email} />
</div>
);
};
export default PasswordManagerApp;
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<PasswordManagerApp />);
推荐:完全受控组件 解决上述问题的一种方法是完全去除组件的状态。如果电子邮件地址仅作为一个属性存在,那么我们就不必担心与状态的冲突。我们甚至可以将 EmailInput 转换为一个更轻量级的函数组件:
import React, { Component } from "react";
import ReactDOM from "react-dom/client";
class EmailInput extends Component {
render() {
return <input onChange={this.props.handleChange} value={this.props.email} />;
}
}
class App extends Component {
state = {
email: "123456@qq.com",
};
handleChange = (event) => {
this.setState({
email: event.target.value,
});
};
render() {
return (
<div>
<EmailInput email={this.state.email} handleChange={this.handleChange}/>
{}
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
另一种替代方案是让我们的组件完全掌控“草稿”电子邮件状态。在这种情况下,我们的组件仍然可以接受一个属性作为初始值,但它会忽略对该属性的后续更改:
import React, { Component } from "react";
import ReactDOM from "react-dom/client";
class EmailInput extends Component {
state = { email: this.props.defaultEmail };
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}
class App extends Component {
state = {
email: "123456@qq.com",
};
render() {
return (
<div>
<EmailInput defaultEmail={this.state.email} />
{}
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
我们也见过派生状态用于确保在渲染中使用的昂贵值仅在输入改变时重新计算。这种技术被称为记忆化。
使用派生状态进行记忆化并不一定是坏事,但通常不是最佳解决方案。管理派生状态本身就有固有的复杂性,而且随着额外属性的增加,这种复杂性会增加。例如,如果我们在组件状态中添加第二个派生字段,那么我们的实现就需要分别跟踪这两个字段的变化。
让我们看一个例子,这个组件接受一个属性——一个项目列表——并渲染与用户输入的搜索查询匹配的项目。我们可以使用派生状态来存储过滤后的列表:
import React, { Component, Fragment } from 'react';
import ReactDOM from "react-dom/client";
class Todos extends Component {
state = {
filterText: "",
filteredList: this.props.list,
prevPropsList: this.props.list,
prevFilterText: "",
};
static getDerivedStateFromProps(props, state) {
if (
props.list !== state.prevPropsList ||
state.prevFilterText !== state.filterText
) {
return {
prevPropsList: props.list,
prevFilterText: state.filterText,
filteredList: props.list.filter(item => item.text.includes(state.filterText))
};
}
return null;
}
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
return (
<>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{this.state.filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</>
);
}
}
class App extends Component {
render() {
const list = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
];
return <Todos list={list} />;
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
这种实现避免了不必要地频繁重新计算filteredList。但是它比实际需要的更复杂,因为它必须分别跟踪并检测属性和状态的变化,以便正确更新过滤后的列表。在这个例子中,我们可以通过使用PureComponent并将过滤操作移动到render方法中来简化事情:
import React, { Component, Fragment } from "react";
import ReactDOM from "react-dom/client";
class Todos extends PureComponent {
state = {
filterText: ""
};
handleChange = (event) => {
this.setState({ filterText: event.target.value });
};
render() {
+ const filteredList = this.props.list.filter(item => item.text.includes(this.state.filterText))
return (
<>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>
{filteredList.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</>
);
}
}
class App extends Component {
render() {
const list = [
{ id: 1, text: "Item 1" },
{ id: 2, text: "Item 2" },
{ id: 3, text: "Item 3" },
];
return <Todos list={list} />;
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
这种方法比派生状态版本更简洁、更清晰。但有时这还不够好——对于大型列表,过滤可能会很慢,而且如果另一个属性发生变化,PureComponent也无法阻止重新渲染。为了解决这两个问题,我们可以添加一个备忘录辅助功能,以避免不必要地重新过滤我们的列表:
memoize-one
是一个 JavaScript 库,用于创建一个记忆化(memoized)函数。这意味着当你用相同的参数多次调用该函数时,它只会在第一次计算结果,之后会返回缓存的结果。这在处理重计算成本高的函数时非常有用,比如渲染、复杂的计算等。
import React, { Component, PureComponent } from "react";
import ReactDOM from "react-dom/client";
+function memoizeOne(func) {
+ let lastArgs = null;
+ let lastResult = null;
+ return function(...args) {
+
+ if (lastArgs !== null && args.length === lastArgs.length && args.every((val, index) => val === lastArgs[index])) {
+ return lastResult;
+ }
+ lastArgs = args;
+ lastResult = func.apply(this, args);
+ return lastResult;
+ };
+}
class Todos extends PureComponent {
state = {
filterText: ""
};
+ filter = memoizeOne(
+ (list, filterText) => list.filter(item => item.text.includes(filterText))
+ );
handleChange = (event) => {
this.setState({ filterText: event.target.value });
};
render() {
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>
{filteredList.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</>
);
}
}
class App extends Component {
render() {
const list = [
{ id: 1, text: "Item 1" },
{ id: 2, text: "Item 2" },
{ id: 3, text: "Item 3" },
];
return <Todos list={list} />;
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
React 中的 ErrorBoundary
是一种 React 组件,用于捕获其子组件树中的 JavaScript 错误,并记录这些错误,并显示一个回退的 UI,而不是使整个组件树崩溃。Error boundaries
仅捕获其子组件树中的错误,无法捕获其自身的错误。
要使用 Error Boundary
,你需要创建一个类组件,并在其中定义至少一个错误生命周期方法,即 static getDerivedStateFromError()
或 componentDidCatch()
。
static getDerivedStateFromError(error)
:此生命周期方法在后代组件抛出错误后被调用。它应返回一个值以更新 state,以便下次渲染时可以显示回退 UI。componentDidCatch(error, errorInfo)
:此生命周期方法在后代组件抛出错误后被调用。它可用于记录错误信息。下面是一个 ErrorBoundary
组件的示例,以及如何在应用程序中使用它:
import React from "react";
import ReactDOM from "react-dom/client";
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
};
}
static getDerivedStateFromError(error) {
return {
hasError: true,
};
}
componentDidCatch(error, errorInfo) {
console.error("Uncaught error:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function Main() {
console.log(undefined.a);
return <h1>Main</h1>;
}
class ParentComponent extends React.Component {
render() {
return (
<div>
<div>Header</div>
<ErrorBoundary>
<Main />
</ErrorBoundary>
<div>Footer</div>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ParentComponent />);
在上述示例中,ErrorBoundary
组件会在其子组件(这里是 SomeOtherComponent
)出错时,显示一个简单的错误信息。你可以根据需要自定义这个回退 UI。需要注意的是,Error Boundary 并不能捕获事件处理器内部的错误,异步代码(例如 setTimeout
或 requestAnimationFrame
回调函数),服务器端渲染,它自己抛出来的错误(而不是其子组件)。
相同点
不同点
useState
, useEffect
),类组件不能使用这些钩子。this.state
和 this.setState
管理状态,而函数组件可以通过 useState
钩子管理状态。this
关键字: 类组件中经常需要使用 this
来访问 props、状态和类方法。而函数组件中没有 this
,一切都通过函数参数或钩子访问。componentDidMount
, componentDidUpdate
, componentWillUnmount
),而函数组件则使用钩子如 useEffect
来模拟这些生命周期。static contextTypes
使用Context
,函数组件使用Consumer
和useContext
在React中,逻辑复用可以通过几种不同的方式实现,这取决于你是在使用函数组件还是类组件。下面是一些常见的方法:
React 的自定义 Hooks 是 React 功能的一个扩展,允许你在函数组件中重用状态逻辑,而无需改变组件结构。自定义 Hooks 本质上是 JavaScript 函数,但它们遵循一些特定的规则并利用 React 的基础 Hooks(如 useState
, useEffect
, useContext
等)。
以下是自定义 Hooks 的一些关键点:
重用状态逻辑:自定义 Hooks 允许你将组件逻辑提取到可重用的函数中。这意味着相同的状态管理和副作用逻辑可以在多个组件中共享,而无需重复代码。
命名约定:自定义 Hooks 应以 use
开头,这不仅是一种命名约定,也是 React 自动应用 Hooks 规则的一部分。
封装和组织:通过自定义 Hooks,你可以将相关逻辑组织在一起,使代码更易于理解和维护。例如,你可以创建一个 useForm
Hook 来处理表单输入和验证。
使用基础 Hooks:自定义 Hooks 可以调用其他 Hooks,这意味着你可以在自定义 Hook 中使用 useState
, useEffect
, useContext
等基础 Hooks。
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
const useForm = (initialValues) => {
const [values, setValues] = useState(initialValues);
const handleChange = (event) => {
const { name, value } = event.target;
setValues({
...values,
[name]: value,
});
};
const handleSubmit = (event) => {
event.preventDefault();
console.log(values);
};
return { values, handleChange, handleSubmit };
};
const LoginForm = () => {
const { values, handleChange, handleSubmit } = useForm({ username: "", password: "" });
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username</label>
<input
type="text"
name="username"
value={values.username}
onChange={handleChange}
/>
</div>
<div>
<label>Password</label>
<input
type="password"
name="password"
value={values.password}
onChange={handleChange}
/>
</div>
<button type="submit">Login</button>
</form>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<LoginForm />);
高阶组件(HOCs)是 React 中用于重用组件逻辑的一种高级技术。简单来说,它们是参数为组件、返回值为新组件的函数。HOCs 让你可以把组件当作参数传入,然后返回一个新的“增强”组件,这个新组件拥有额外的属性或行为。
这里有几个关键点来理解高阶组件:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
const withLoading = WrappedComponent => {
return props => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setIsLoading(false);
}, 2000);
}, []);
if (isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
};
const MyComponent = () => {
return <div>内容加载完成!</div>;
};
const MyComponentWithLoading = withLoading(MyComponent);
const App = () => {
return<MyComponentWithLoading />;
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
使用一个值为函数的prop(属性),这个函数返回要渲染的React元素。通过这种方式,可以在多个组件间共享逻辑。
import React, { useState, useEffect, Component } from "react";
import ReactDOM from "react-dom/client";
class MouseTracker extends Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0,
};
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
};
render() {
return (
<div
style={{
height: "100vh",
}}
onMouseMove={this.handleMouseMove}
>
{this.props.render(this.state)}
</div>
);
}
}
const App = () => (
<div>
<h1>Move the mouse around!</h1>
<MouseTracker
render={({ x, y }) => (
<p>
The current mouse position is ({x}, {y})
</p>
)}
/>
</div>
);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
函数组件一样,HOCs也可以用于类组件来封装并复用逻辑
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
function withAuthentication(WrappedComponent) {
class WithAuthentication extends React.Component {
isLoggedIn() {
return false;
}
render() {
if (!this.isLoggedIn()) {
return <div>Please log in to view this page.</div>;
}
return <WrappedComponent {...this.props} />;
}
}
return WithAuthentication;
}
class MyComponent extends React.Component {
render() {
return <div>Welcome to the protected page!</div>;
}
}
const ProtectedMyComponent = withAuthentication(MyComponent);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ProtectedMyComponent />);
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
//import hoistNonReactStatics from 'hoist-non-react-statics';
function hoistNonReactStatics(targetComponent, sourceComponent) {
const REACT_STATICS = {
displayName: true,
propTypes: true,
defaultProps: true,
contextTypes: true,
childContextTypes: true,
};
Object.getOwnPropertyNames(sourceComponent).forEach((key) => {
if (!REACT_STATICS[key]) {
try {
if (
!Object.getOwnPropertyDescriptor(targetComponent, key) || Object.getOwnPropertyDescriptor(targetComponent, key).configurable
) {
Object.defineProperty(
targetComponent,
key,
Object.getOwnPropertyDescriptor(sourceComponent, key)
);
}
} catch (error) {
console.log(error);
}
}
});
return targetComponent;
}
function withAuthentication(WrappedComponent) {
class WithAuthentication extends React.Component {
isLoggedIn() {
return false;
}
render() {
if (!this.isLoggedIn()) {
return <div>Please log in to view this page.</div>;
}
return <WrappedComponent {...this.props} />;
}
}
hoistNonReactStatics(WithAuthentication, WrappedComponent);
return WithAuthentication;
}
class MyComponent extends React.Component {
static someStaticProperty = "Some Value";
static someStaticMethod() {
console.log(`someStaticMethod`);
}
render() {
return <div>Welcome to the protected page!</div>;
}
}
const ProtectedMyComponent = withAuthentication(MyComponent);
console.log(ProtectedMyComponent.someStaticProperty);
ProtectedMyComponent.someStaticMethod();
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ProtectedMyComponent />);
高阶组件(HOC)在React中是一种重用组件逻辑的常见模式。然而,它们有一个常见的问题:refs
属性不会像其他props一样自动透传到被包裹的组件。这是因为ref
不是一个prop,而是一个由React特别处理的属性,用于获取组件实例或DOM元素的引用。
为什么refs
不会透传?
当你在父组件中使用ref
时,React会处理这个ref
并将其附加到相应的元素或组件实例上,而不是将其作为prop传递。这意味着,如果你将ref
作为prop传递给HOC,HOC本身会捕获这个ref
,而不是传递它到子组件。
解决方案:使用React.forwardRef
要解决这个问题,你可以使用React.forwardRef
。这个API允许你将ref
自动转发到另一个组件。这样,即使是在HOC中,ref
也可以正确地传递给被包裹的组件。
import React from "react";
import ReactDOM from "react-dom/client";
class MyComponent extends React.Component {
render() {
return (
<div>
<h1>Hello, {this.props.name}</h1>
</div>
);
}
}
function withHOC(Component) {
return React.forwardRef((props, forwardedRef) => (
<Component {...props} ref={forwardedRef} />
));
}
const EnhancedComponent = withHOC(MyComponent);
class App extends React.Component {
myComponentRef = React.createRef();
componentDidMount() {
console.log(this.myComponentRef.current);
}
render() {
return <EnhancedComponent name="World" ref={this.myComponentRef} />;
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
链式调用高阶组件是React中一个常见的模式,它允许你将多个高阶组件(HOCs)组合在一起,为组件提供额外的功能。
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import hoistNonReactStatics from 'hoist-non-react-statics';
+function withLogging(WrappedComponent) {
+ return class extends React.Component {
+ componentDidMount() {
+ console.log(`${WrappedComponent.name} has been mounted`);
+ }
+ componentWillUnmount() {
+ console.log(`${WrappedComponent.name} will be unmounted`);
+ }
+ render() {
+ return <WrappedComponent {...this.props} />;
+ }
+ };
+}
function withAuthentication(WrappedComponent) {
class WithAuthentication extends React.Component {
isLoggedIn() {
return false;
}
render() {
if (!this.isLoggedIn()) {
return <div>Please log in to view this page.</div>;
}
return <WrappedComponent {...this.props} />;
}
}
hoistNonReactStatics(WithAuthentication, WrappedComponent);
return WithAuthentication;
}
class MyComponent extends React.Component {
static someStaticProperty = "Some Value";
static someStaticMethod() {
console.log(`someStaticMethod`);
}
render() {
return <div>Welcome to the protected page!</div>;
}
}
+const EnhancedMyComponent = withLogging(withAuthentication(MyComponent));
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<EnhancedMyComponent />);
渲染劫持是高阶组件(HOC)中的一个概念,它指的是HOC在不改变被包裹组件本身的情况下,改变其渲染输出的能力。这可以包括条件渲染、添加额外的props、甚至是完全改变渲染结构。
渲染劫持的用途
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
const MyComponent = (props) => (
<div>
<h1>Hello, {props.name}</h1>
</div>
);
function withRenderHijacking(WrappedComponent) {
return class extends React.Component {
render() {
if(this.props.loading){
return <div>Loading...</div>
}
const elementsTree = <WrappedComponent {...this.props} />;
if (this.props.hijack) {
return (
<div style={{ color: "red" }}>
<h2>Hijacked!</h2>
{React.cloneElement(elementsTree, {
...this.props,
name: "Hijacked Name",
style:{color:'red'}
})}
</div>
);
}
return elementsTree;
}
};
}
const HijackedMyComponent = withRenderHijacking(MyComponent);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<HijackedMyComponent hijack loading={false} />);
还可以通过继承实现
import React from "react";
import ReactDOM from "react-dom/client";
class ClassMyComponent extends React.Component {
render() {
return (
<div>
<h1>Hello, {this.state.name}</h1>
</div>
);
}
}
function withRenderHijacking(WrappedComponent) {
return class extends WrappedComponent {
state={name:"Hijacked Name"}
render() {
if (this.props.loading) {
return <div>Loading...</div>;
}
const elementsTree = super.render();
if (this.props.hijack) {
return (
<div style={{color: "red"}}>
<h2>Hijacked!</h2>
{React.cloneElement(elementsTree, {
...this.props
})}
</div>
);
}
return elementsTree;
}
};
}
const HijackedMyComponent = withRenderHijacking(ClassMyComponent);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<HijackedMyComponent hijack loading={false} />);
React类组件可以通过继承基类组件来复用逻辑
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
class BaseComponent extends React.Component {
static sharedStaticProperty = "Shared Value";
constructor(props) {
super(props);
this.state = {
sharedState: "Initial State",
};
}
sharedMethod() {
console.log("Shared method called");
}
render() {
return (
<div>
<h2>Base Component</h2>
<p>Static Property: {this.constructor.sharedStaticProperty}</p>
<p>State: {this.state.sharedState}</p>
</div>
);
}
}
class DerivedComponent extends BaseComponent {
componentDidMount() {
this.sharedMethod();
}
render() {
return (
<div>
<h2>Derived Component</h2>
<p>Static Property: {this.constructor.sharedStaticProperty}</p>
<p>State: {this.state.sharedState}</p>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<DerivedComponent />);
在React早期版本中,Mixins
被用来共享组件逻辑,但由于各种问题,现在已不推荐使用
无状态组件,也称为功能性组件(Functional Components),是React中最简单的组件形式。它们仅依赖于传入的props
来显示内容或行为,并不管理或维护内部状态(即没有state
)。
特点:
this.state
)。props
并返回React元素。使用场景:
示例:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
代理组件(Proxy Component)是一种设计模式,它允许你在不修改原始组件代码的情况下增强或修改该组件的行为
通过这种对第三方组件的封装还可以隔离第三方组件。可以方便以后重构替换和实现公共逻辑
import React from "react";
import ReactDOM from "react-dom/client";
import { Button } from "antd";
class ButtonWithLogging extends React.Component {
handleClick = (e) => {
if (this.props.onClick) {
this.props.onClick(e);
}
console.log("Button was clicked!");
};
render() {
return <Button type="primary" {...this.props} onClick={this.handleClick} />;
}
}
const App = () => (
<div>
<ButtonWithLogging type="primary">Click me</ButtonWithLogging>
</div>
);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
在React中使用样式组件(Styled Components)是一种流行的方式来封装和复用组件的样式
styled-components
库允许你编写实际的CSS代码来为你的React组件设置样式,同时保持了样式与组件的封装性。
import React from "react";
import ReactDOM from "react-dom/client";
//import styled from 'styled-components';
const styled = {
h1: styles => {
return props => <h1 style={parseStyles(styles[0])} {...props} />;
},
button: styles => {
return props => <button style={parseStyles(styles[0])} {...props} />;
}
};
function convertToCamelCase(cssProperty) {
return cssProperty.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
function parseStyles(styles) {
return styles.split(";").reduce((styleObject, style) => {
const [property, value] = style.split(":");
if (property && value) {
styleObject[convertToCamelCase(property.trim())] = value.trim();
}
return styleObject;
}, {});
}
const Title = styled.h1`
color: palevioletred;
`;
const StyledButton = styled.button`
background-color: palevioletred;
color: white;
`;
const App = () => (
<div>
<Title>Welcome to Styled Components!</Title>
<StyledButton>Click me</StyledButton>
</div>
);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
布局组件是一种常见的模式,用于封装和复用页面布局
import React from "react";
import ReactDOM from "react-dom/client";
const Header = () => (
<div
style={{
background: "lightblue",
padding: "10px",
textAlign: "center",
}}
>
<h1>Header</h1>
</div>
);
const Footer = () => (
<div
style={{
background: "lightgreen",
padding: "10px",
textAlign: "center",
}}
>
<p>Footer</p>
</div>
);
const MainContent = () => (
<div
style={{
padding: "20px",
}}
>
MainContent
</div>
);
const Layout = React.memo(() => (
<div>
<Header />
<MainContent />
<Footer />
</div>
));
const App = () => <Layout />;
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
有状态组件是指在其内部维护一个或多个状态的React组件。这些状态的变化可能会导致组件重新渲染。
特点:
this.state
和this.setState
)。componentDidMount
, componentDidUpdate
, 等。使用场景:
在React中,容器组件是一种专门用于处理数据获取和业务逻辑的组件,而展示组件则关注于如何展示这些数据。容器组件负责从外部源(如API、Redux store等)获取数据,然后将这些数据传递给展示组件。
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';
const UserList = ({ users}) => <div>
<h2>User List</h2>
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
</div>;
const UserListContainer = () => {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setUsers([{id: 1,name: '张三'}, {id: 2,name: '李四'}]);
setIsLoading(false);
}, 1000);
}, []);
if (isLoading) {
return <div>Loading...</div>;
}
return <UserList users={users} />;
};
const App = () => <div>
<UserListContainer />
</div>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
src/
|-- hoc/ // 高阶组件
|-- components/ // 通用组件
| |-- Button/
| | |-- index.js // Button组件
| | |-- styles.css // Button的样式
|-- pages/ // 页面
| |-- pageA/
| | |-- components/ // pageA的专用组件
| | |-- hooks/ // pageA的hooks
| | |-- index.js // pageA的主组件
| |-- FeatureB/
| | ...
|-- hooks/ // 通用的自定义hooks
|-- utils/ // 工具函数
|-- app.js // 应用入口组件
|-- index.js // ReactDOM渲染入口
在React中,父组件向子组件传递数据通常是通过props(属性)来实现的。这是一种单向数据流,确保了组件间的数据传输清晰且易于追踪。
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
class ChildComponent extends React.Component {
render() {
return <div>{this.props.text}</div>;
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "加载中...",
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
text: "从网络请求获得的文案",
});
}, 2000);
}
render() {
return (
<div>
<h1>父组件</h1>
<ChildComponent text={this.state.text} />
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ParentComponent />);
在React中,子组件向父组件传递数据通常通过回调函数来实现。父组件定义一个函数并通过props传递给子组件,子组件在某个事件(如用户操作)发生时调用这个回调函数,从而将数据传递回父组件。
import React from "react";
import ReactDOM from "react-dom/client";
class ChildComponent extends React.Component {
sendDataToParent = () => {
this.props.onReceiveData("子组件发送的数据");
};
render() {
return <button onClick={this.sendDataToParent}>发送数据给父组件</button>;
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
dataFromChild: "",
};
}
handleDataFromChild = (data) => {
this.setState({
dataFromChild: data,
});
};
render() {
return (
<div>
<h1>父组件</h1>
<ChildComponent onReceiveData={this.handleDataFromChild} />
<p>接收到的子组件数据:{this.state.dataFromChild}</p>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ParentComponent />);
import React,{Component} from "react";
import ReactDOM from "react-dom/client";
class ModalComponent extends Component {
constructor(props) {
super(props);
this.state = { isVisible: false };
}
show = () => {
this.setState({ isVisible: true });
};
hide = () => {
this.setState({ isVisible: false });
};
render() {
const { isVisible } = this.state;
return (
<div style={{ display: isVisible ? "block" : "none" }}>
<h2>模态窗口</h2>
<button onClick={this.hide}>关闭</button>
</div>
);
}
}
class ParentComponent extends Component {
modalRef = React.createRef();
openModal = () => {
this.modalRef.current.show();
};
render() {
return (
<div>
<button onClick={this.openModal}>打开模态窗口</button>
<ModalComponent ref={this.modalRef} />
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ParentComponent />);
在React中,兄弟组件之间直接传递数据较为复杂,因为React的数据流是单向的,从父组件向子组件流动。因此,当需要在兄弟组件之间传递数据时,通常通过它们共同的父组件来协调这一过程。具体来说,一个兄弟组件将数据传递给父组件,然后父组件再将这些数据传递给另一个兄弟组件。
import React from "react";
import ReactDOM from "react-dom/client";
class SiblingComponentOne extends React.Component {
sendData = () => {
this.props.onSendData("来自兄弟组件一的数据");
};
render() {
return <button onClick={this.sendData}>发送数据到兄弟组件二</button>;
}
}
class SiblingComponentTwo extends React.Component {
render() {
return <div>从兄弟组件一接收的数据:{this.props.data}</div>;
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: "",
};
}
handleData = (data) => {
this.setState({
data: data,
});
};
render() {
return (
<div>
<h1>父组件</h1>
<SiblingComponentOne onSendData={this.handleData} />
<SiblingComponentTwo data={this.state.data} />
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<ParentComponent />);
React的Context API允许我们跨组件树传递数据,非常适合用于管理应用的本地化(国际化)设置
import React,{useState,useContext} from "react";
import ReactDOM from "react-dom/client";
const LanguageContext = React.createContext();
export const LanguageProvider = ({ children }) => {
const [language, setLanguage] = useState("en");
const switchLanguage = (lang) => {
setLanguage(lang);
};
return (
<LanguageContext.Provider value={{ language, switchLanguage }}>
{children}
</LanguageContext.Provider>
);
};
const texts = {
en: {
greeting: "Hello",
farewell: "Goodbye",
},
cn: {
greeting: "你好",
farewell: "再见",
},
};
const LanguageSwitcher = () => {
const { switchLanguage } = useContext(LanguageContext);
return (
<div>
<button onClick={() => switchLanguage("en")}>English</button>
<button onClick={() => switchLanguage("cn")}>中文</button>
</div>
);
};
const LocalizedText = () => {
const { language } = useContext(LanguageContext);
return (
<div>
<p>{texts[language].greeting}</p>
<p>{texts[language].farewell}</p>
</div>
);
};
const App = () => {
return (
<LanguageProvider>
<LanguageSwitcher />
<LocalizedText />
</LanguageProvider>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
import React,{useState,useContext} from "react";
import ReactDOM from "react-dom/client";
const LanguageContext = React.createContext();
const withLanguage = (WrappedComponent) => {
return (props) => {
const { language, switchLanguage } = useContext(LanguageContext);
return <WrappedComponent language={language} switchLanguage={switchLanguage} {...props} />;
};
};
const LanguageSwitcher = withLanguage(({ switchLanguage }) => {
return (
<div>
<button onClick={() => switchLanguage('en')}>English</button>
<button onClick={() => switchLanguage('cn')}>中文</button>
</div>
);
});
const LocalizedText = withLanguage(({ language }) => {
return (
<div>
<p>{texts[language].greeting}</p>
<p>{texts[language].farewell}</p>
</div>
);
});
export const LanguageProvider = ({ children }) => {
const [language, setLanguage] = useState("en");
const switchLanguage = (lang) => {
setLanguage(lang);
};
return (
<LanguageContext.Provider value={{ language, switchLanguage }}>
{children}
</LanguageContext.Provider>
);
};
const texts = {
en: {
greeting: "Hello",
farewell: "Goodbye",
},
cn: {
greeting: "你好",
farewell: "再见",
},
};
const App = () => {
return (
<LanguageProvider>
<LanguageSwitcher />
<LocalizedText />
</LanguageProvider>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
const globalData = {
message: "",
};
const MessageSetter = () => {
const setMessage = (msg) => {
globalData.message = msg;
window.dispatchEvent(new Event("globalDataChanged"));
};
return (
<div>
<button onClick={() => setMessage("Hello from Setter!")}>
Set Message
</button>
</div>
);
};
const MessageViewer = () => {
const [message, setMessage] = useState(globalData.message);
useEffect(() => {
const handler = () => setMessage(globalData.message);
window.addEventListener("globalDataChanged", handler);
return () => {
window.removeEventListener("globalDataChanged", handler);
};
}, []);
return (
<div>
<p>Message: {message}</p>
</div>
);
};
const App = () => {
return (
<div>
<MessageSetter />
<MessageViewer />
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
React的生态系统中出现了多种第三方状态管理库,它们各自以不同的方式解决了React应用中的状态管理问题。从它们首次出现的顺序来看,以下是一些最常见的状态管理库:
Flux(2014年)
Redux(2015年)
MobX(2016年)
Recoil(2020年)
Zustand(2020年)
Jotai(2020年)
Valtio(2020年)
FPS指的是“每秒帧数”(Frames Per Second)或“每秒画面数”。这是衡量视频、游戏、动画或任何动态视觉媒体流畅度的关键指标 计算 Web 页面的每秒帧数(FPS)通常涉及跟踪浏览器在单位时间内能渲染多少帧。
FPS 范围及其体验
流畅(Smooth):
可接受(Acceptable):
卡顿(Choppy):
计算 Web 页面的 FPS(每秒帧数)通常需要使用浏览器的性能监测工具或编写特定的 JavaScript 代码。下面是一种常用的方法来计算网页的 FPS:
定义变量:
使用 requestAnimationFrame
:
requestAnimationFrame
方法用于告诉浏览器你希望执行动画,并请求浏览器在下次重绘之前调用你指定的回调函数。这个回调函数的执行频率通常与浏览器的重绘频率一致。计算帧数:
requestAnimationFrame
的回调函数被执行时,增加帧数的计数。setInterval
),每秒计算一次过去一秒内的帧数,然后重置帧数计数器。显示 FPS:
let frameCount = 0;
let lastSecond = Date.now();
let fps = 0;
function updateFrameCount() {
frameCount++;
const now = Date.now();
if (now - lastSecond >= 1000) {
fps = frameCount;
frameCount = 0;
lastSecond = now;
console.log(`FPS: ${fps}`);
}
requestAnimationFrame(updateFrameCount);
}
requestAnimationFrame(updateFrameCount);
React 组件的重新渲染(re-render)通常是由于其内部状态(state)或传入的属性(props)发生变化。以下是触发 React 组件重新渲染的主要情况:
状态(State)变化:
setState
方法更新时,React 会安排重新渲染。setState
增加计数,这将导致组件重新渲染。属性(Props)变化:
强制渲染(Force Update):
forceUpdate
方法可以强制组件进行重新渲染,尽管这种做法并不推荐,因为它绕过了 React 的正常更新机制。父组件重新渲染:
React.memo
或 shouldComponentUpdate
)。Context 变化:
使用 Hooks 导致的渲染:
useState
, useReducer
, useContext
)在其依赖的数据变化时会触发组件的重新渲染。为了优化性能,避免不必要的渲染,可以使用 React.memo
用于函数组件,或在类组件中使用 shouldComponentUpdate
生命周期方法。这些方法可以帮助你确定在特定的 props 或 state 更新时是否需要重新渲染组件。
快速定位组件对应的真实DOM
VS Code
@welldone-software/why-did-you-render
是一个用于React应用的开发者工具,其主要目的是帮助开发者识别和避免不必要的组件重新渲染,从而优化React应用的性能。这个库通过跟踪组件的渲染并在检测到可能的性能问题时提供警告和详细信息,使开发者能够更容易地理解和优化他们的组件。
主要特性
检测不必要的渲染:
详细的日志信息:
灵活的配置选项:
支持函数组件和类组件:
与React开发者工具集成:
使用场景
注意事项
import React from "react";
import ReactDOM from "react-dom/client";
//const whyDidYouRender = require("@welldone-software/why-did-you-render");
function whyDidYouRender(React) {
const originalCreateElement = React.createElement;
React.createElement = (type, props, ...children) => {
if (type.prototype instanceof React.Component) {
type.prototype.componentDidUpdate = function (prevProps, prevState) {
const newProps = this.props;
const newState = this.state;
const propsChanged = !deepCompare(newProps, prevProps);
const stateChanged = !deepCompare(newState, prevState);
if (!(propsChanged || stateChanged)) {
console.log(
`[why-did-you-render] ${type.name} re-rendered without any changes in props or state.`
);
}
};
return originalCreateElement(type, props, ...children);
}
return originalCreateElement(type, props, ...children);
};
}
function deepCompare(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (
typeof obj1 !== "object" ||
typeof obj2 !== "object" ||
obj1 == null ||
obj2 == null
) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
if (!keys2.includes(key) || !deepCompare(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
whyDidYouRender(React, {
trackAllPureComponents: true,
});
class BigPureComponent extends React.PureComponent {
render() {
console.log("BigPureComponent render", this.props.styles);
return <div style={this.props.styles}>BigPureComponent:</div>;
}
}
class ParentComponent extends React.Component {
state = { count: 0 };
incrementCount = () => {
this.setState((prevState) => ({
count: prevState.count + 1,
}));
};
render() {
return (
<div>
<button onClick={this.incrementCount}>{this.state.count}</button>
<BigPureComponent styles={{ width: Date.now() + `100%` }} />
</div>
);
}
}
ReactDOM.createRoot(document.getElementById("root")).render(
<ParentComponent />
);
PureComponent
是 React 中的一个特殊类型的组件,提供了一种性能优化的方法。这里是它的关键特点:
1. 浅比较 Props 和 State
props
或 state
发生变化时,React 将重新渲染该组件及其子组件。这是通过 shouldComponentUpdate
生命周期方法来决定的,该方法默认返回 true
,意味着任何 props
或 state
的变化都会触发重新渲染。PureComponent
通过浅比较(shallow comparison)来优化这个过程。如果 props
或 state
的表层数据没有改变,PureComponent
将不会重新渲染。这是因为它实现了一个浅比较的 shouldComponentUpdate
方法。2. 浅比较的工作原理
{ a: 1, b: { c: 2 } }
,并且更改了 b.c
的值,浅比较将认为状态没有变化,因为顶层属性 a
和 b
的引用没有变化。3. 使用场景
props
或 state
,并且经常发生更新时,使用 PureComponent
可以提升性能。props
和 state
的结构比较简单,或者更新频率较低,那么使用 PureComponent
的优势就不那么明显。4. 注意事项
PureComponent
进行的是浅比较,因此它可能不会检测到深层对象结构的变化。这意味着在更新对象或数组时,你应该创建一个新的对象或数组,而不是修改现有的。props
传递时。总的来说,PureComponent
是一个用于优化 React 应用性能的有力工具,但需要谨慎使用,以确保它的使用场景和数据结构适合进行浅比较。
import React from "react";
import ReactDOM from "react-dom/client";
class ChildCounter extends React.PureComponent {
render() {
console.log("ChildCounter render");
return (
<button onClick={this.props.add}>{this.props.count.number}</button>
);
}
}
class App extends React.Component {
state = {
count: { number: 0 },
text: ""
};
add = () => {
this.setState({ count: { number: this.state.count.number + 1 } });
}
setText = (event) => {
this.setState({ text: event.target.value });
}
render() {
return (
<div>
<input value={this.state.text} onChange={this.setText} />
<ChildCounter count={this.state.count} add={this.add} />
</div>
);
}
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
import React from "react";
import ReactDOM from "react-dom/client";
+function deepCompare(obj1, obj2) {
+ if (obj1 === obj2) {
+ return true;
+ }
+ if (
+ typeof obj1 !== "object" ||
+ obj1 === null ||
+ typeof obj2 !== "object" ||
+ obj2 === null
+ ) {
+ return false;
+ }
+ const keys1 = Object.keys(obj1);
+ const keys2 = Object.keys(obj2);
+ if (keys1.length !== keys2.length) {
+ return false;
+ }
+ for (let key of keys1) {
+ if (typeof obj1[key] === "object" && typeof obj2[key] === "object") {
+ if (!deepCompare(obj1[key], obj2[key])) {
+ return false;
+ }
+ } else if (obj1[key] !== obj2[key]) {
+ return false;
+ }
+ }
+ return true;
+}
+class ChildCounter extends React.Component {
+ shouldComponentUpdate(nextProps) {
+ console.log(this.props.count, nextProps.count,deepCompare(this.props, nextProps));
+ return !deepCompare(this.props, nextProps);
+ }
render() {
console.log("ChildCounter render");
return (
<button onClick={this.props.add}>{this.props.count.number}</button>
);
}
}
class App extends React.Component {
state = {
count: { number: 0 },
text: ""
};
add = () => {
+ this.setState({ count: {number:this.state.count.number+0} });
}
setText = (event) => {
this.setState({ text: event.target.value });
}
render() {
return (
<div>
<input value={this.state.text} onChange={this.setText} />
<ChildCounter count={this.state.count} add={this.add} />
</div>
);
}
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
import React from "react";
import ReactDOM from "react-dom/client";
+//import {produce} from "immer";
+function produce(recipe) {
+ return function (base) {
+ const draft = JSON.parse(JSON.stringify(base));
+ recipe(draft);
+ return draft;
+ };
+}
class ChildCounter extends React.PureComponent {
render() {
console.log("ChildCounter render");
return <button onClick={this.props.add}>{this.props.count.number}</button>;
}
}
class App extends React.Component {
state = {
count: { number: 0 },
text: "",
};
add = () => {
+ this.setState(
+ produce((draft) => {
+ draft.count.number += 1;
+ })
+ );
};
setText = (event) => {
this.setState({ text: event.target.value });
};
render() {
return (
<div>
<input value={this.state.text} onChange={this.setText} />
<ChildCounter count={this.state.count} add={this.add} />
</div>
);
}
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
import React from "react";
import ReactDOM from "react-dom/client";
import {produce} from "immer";
+//import { createSelector } from "reselect";
+function createSelector(...funcs) {
+ let lastArgs = [];
+ let lastResult;
+ let calledOnce = false;
+ const resultFunc = funcs.pop();
+ return function(...args) {
+ const inputsChanged = lastArgs.length !== args.length ||
+ args.some((arg, index) => arg !== lastArgs[index]);
+ if (calledOnce && !inputsChanged) {
+ return lastResult;
+ }
+ lastArgs = args;
+ lastResult = resultFunc(...args.map((arg, index) => funcs[index](arg)));
+ calledOnce = true;
+ return lastResult;
+ };
+}
class ChildCounter extends React.PureComponent {
render() {
console.log("ChildCounter render");
return <button onClick={this.props.add}>{this.props.doubleCount.number}</button>;
}
}
class App extends React.Component {
state = {
count: { number: 0 },
text: "",
};
add = () => {
this.setState(
produce((draft) => {
draft.count.number += 1;
})
);
};
setText = (event) => {
this.setState({ text: event.target.value });
};
+ doubleCountSelector = createSelector(
+ count => count.number,
+ number => ({number:number * 2})
+ );
render() {
//const doubleCount = {number:this.state.count.number * 2};
+ const doubleCount = this.doubleCountSelector(this.state.count);
return (
<div>
<input value={this.state.text} onChange={this.setText} />
<ChildCounter doubleCount={doubleCount} add={this.add} />
</div>
);
}
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
React Hooks的引入主要是为了解决以下问题:
逻辑复用难度:类组件中的逻辑难以复用。虽然高阶组件(HOCs)和渲染道具(Render Props)提供了一定程度的复用,但它们往往导致代码结构复杂,产生所谓的"回调地狱",难以理解和维护。
组件复杂性:在类组件中,复杂的业务逻辑通常散落在不同的生命周期方法中,这使得理解和维护变得困难。一个生命周期方法可能包含多个不相关的逻辑,这缺乏专注性且增加了混乱。
this关键字的混淆:类组件中的this
关键字经常导致混淆,特别是在事件处理和异步操作中。此外,类组件的方法需要正确绑定this
,这增加了代码的复杂性。
编译优化限制:类组件的某些特性限制了现代JavaScript编译技术(如Tree Shaking)的应用,这影响了最终打包的效率和大小。
Hooks通过提供更简洁的API和更好的函数式编程模式来解决这些问题,使得组件的编写、理解和维护变得更加容易。
只在 React 函数中调用 hooks
不要在循环、条件语句或嵌套函数中调用 Hooks:这是为了保证 Hooks 每次渲染时都以相同的顺序调用,这对于 React 内部追踪 Hooks 状态非常重要。如果在条件语句或循环中使用 Hooks,可能导致每次组件渲染时 Hooks 调用的数量和顺序不一致,进而导致状态管理混乱。
只在 React 函数中调用 Hooks:保证了 Hooks 只在 React 组件的上下文中被使用,这有助于 React 管理组件的生命周期和状态。在组件外部调用 Hooks 可能导致不可预测的行为,因为那些地方没有与 React 组件的生命周期和状态管理机制相结合。
const hookStates = [];
let hookIndex = 0;
export function useState(initVal) {
const currentHookIndex = hookIndex;
if (!hookStates[currentHookIndex]) {
hookStates[currentHookIndex] = [initVal, (newVal)=> {
hookStates[currentHookIndex][0] = newVal;
render();
}];
}
return hookStates[hookIndex++];
}
function FunctionComponent() {
const [firstName] = useState("zhang");
const [lastName, setLastName] = useState("san");
return setLastName;
}
function render() {
hookIndex = 0;
return FunctionComponent();
}
console.log(hookStates.map(item=>item[0]));
const setLastName = render();
console.log(hookStates.map(item=>item[0]));
setLastName("si");
console.log(hookStates.map(item=>item[0]));
const hookStates = [];
let hookIndex = 0;
export function useState(initVal) {
const currentHookIndex = hookIndex;
if (!hookStates[currentHookIndex]) {
hookStates[currentHookIndex] = [
initVal,
(newVal) => {
hookStates[currentHookIndex][0] = newVal;
render();
},
];
}
return hookStates[hookIndex++];
}
let times = 3;
function FunctionComponent() {
for (let i = 0; i < times; i++) {
const [firstName] = useState("zhang");
}
times = 5;
const [lastName, setLastName] = useState("san");
return setLastName;
}
function render() {
hookIndex = 0;
return FunctionComponent();
}
console.log(hookStates.map((item) => item[0]));
const setLastName = render();
console.log(hookStates.map((item) => item[0]));
setLastName("si");
console.log(hookStates.map((item) => item[0]));
useEffect
和 useLayoutEffect
在 React Hooks 中都用于处理副作用,但它们有以下相同点和不同点:
相同点
不同点
执行时机:
useEffect
在整个页面渲染和绘制完成后异步执行,不会阻塞浏览器的绘制过程。useLayoutEffect
与 DOM 更新同步执行,会在浏览器绘制之前执行。因此,如果在 useLayoutEffect
中执行大量操作,可能会导致页面渲染的性能问题。使用场景:
useEffect
适用于大多数副作用场景,如数据获取、订阅、以及在渲染后需要执行的操作。useLayoutEffect
通常用于需要同步读取或更新 DOM 的场景,或者在 DOM 更新后立即需要执行的操作,例如测量布局。简而言之,尽管两者在功能上相似,但 useLayoutEffect
的使用应更加谨慎,以避免因同步执行而引起的性能问题。大多数情况下,使用 useEffect
就足够了。
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom/client";
function App() {
const [value, setValue] = useState("初始值");
const prevValueRef = useRef();
useEffect(() => {
prevValueRef.current = value;
});
const prevValue = prevValueRef.current;
return (
<div>
<p>当前值: {value}</p>
<p>上一轮的值: {prevValue}</p>
<button onClick={() => setValue("新值")}>更新值</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom/client";
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function App() {
const [value, setValue] = useState('初始值');
const prevValue = usePrevious(value);
return <div>
<p>当前值: {value}</p>
<p>上一轮的值: {prevValue}</p>
<button onClick={() => setValue('新值')}>更新值</button>
</div>;
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
import React, {Component, useState} from "react";
import ReactDOM from "react-dom/client";
class ChildComponent extends Component {
intervalId = null;
componentDidMount() {
this.startInterval();
}
componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value) {
this.clearInterval();
this.startInterval();
}
}
componentWillUnmount() {
this.clearInterval();
}
startInterval() {
this.intervalId = setInterval(() => {
console.log(this.props.value);
}, 1000);
}
clearInterval() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
render() {
return <div>
<h2>子组件</h2>
<p>接收的值:{this.props.value}</p>
</div>;
}
}
function ParentComponent() {
const [value, setValue] = useState(0);
return <div>
<h1>父组件</h1>
<button onClick={() => setValue(prev => prev + 1)}>增加值</button>
<ChildComponent value={value} />
</div>;
}
ReactDOM.createRoot(document.getElementById("root")).render(<ParentComponent />);
import React, { useRef, useState ,useEffect} from "react";
import ReactDOM from "react-dom/client";
function ChildComponent({ value }) {
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
console.log(value);
}, 1000);
return () => {
clearInterval(intervalRef.current);
};
}, [value]);
return (
<div>
<h2>子组件</h2>
<p>接收的值:{value}</p>
</div>
);
}
function ParentComponent() {
const [value, setValue] = useState(0);
return (
<div>
<h1>父组件</h1>
<button onClick={() => setValue((prev) => prev + 1)}>增加值</button>
<ChildComponent value={value} />
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(
<ParentComponent />
);
抽取自定义Hook
import React, { useRef, useState ,useEffect} from "react";
import ReactDOM from "react-dom/client";
function useIntervalLogger(value) {
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
console.log(value);
}, 1000);
return () => {
clearInterval(intervalRef.current);
};
}, [value]);
}
function ChildComponent({ value }) {
useIntervalLogger(value);
return (
<div>
<h2>子组件</h2>
<p>接收的值:{value}</p>
</div>
);
}
function ParentComponent() {
const [value, setValue] = useState(0);
return (
<div>
<h1>父组件</h1>
<button onClick={() => setValue((prev) => prev + 1)}>增加值</button>
<ChildComponent value={value} />
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(
<ParentComponent />
);
React.useMemo
和 React.memo
虽然都用于优化性能,但它们服务于不同的场景和目的,所以不能直接比较谁更好。它们的有效性取决于你正在处理的具体问题和使用场景。
React.useMemo
useMemo
可以避免在每次渲染时重新计算。React.memo
React.memo
可以防止这种不必要的渲染。React.memo
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
function ChildComponent({ firstProp, secondProp }) {
console.log("子组件渲染");
return (
<div>
<h2>子组件</h2>
<p>第一个属性:{firstProp}</p>
<p>第二个属性:{secondProp}</p>
</div>
);
}
const ChildComponentMemo = React.memo(
ChildComponent,
(prevProps, nextProps) => {
return prevProps.firstProp === nextProps.firstProp;
}
);
function ParentComponent() {
const [firstProp, setFirstProp] = useState(0);
const [secondProp, setSecondProp] = useState("初始值");
return (
<div>
<h1>父组件</h1>
<button onClick={() => setFirstProp((prev) => prev + 1)}>
改变第一个属性
</button>
<button onClick={() => setSecondProp((prev) => prev + " 更新")}>
改变第二个属性
</button>
<ChildComponentMemo firstProp={firstProp} secondProp={secondProp} />
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(
<ParentComponent />
);
React.useMemo
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
function ChildComponent({ firstProp, secondProp }) {
console.log("子组件渲染");
return (
<div>
<h2>子组件</h2>
<p>第一个属性:{firstProp}</p>
<p>第二个属性:{secondProp}</p>
</div>
);
}
function ParentComponent() {
const [firstProp, setFirstProp] = useState(0);
const [secondProp, setSecondProp] = useState("初始值");
const ChildComponentMemo = React.useMemo(()=>(
<ChildComponent firstProp={firstProp} secondProp={secondProp} />
),[firstProp]);
return (
<div>
<h1>父组件</h1>
<button onClick={() => setFirstProp((prev) => prev + 1)}>
改变第一个属性
</button>
<button onClick={() => setSecondProp((prev) => prev + " 更新")}>
改变第二个属性
</button>
{ChildComponentMemo}
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(
<ParentComponent />
);
在使用React Hooks时。可以使用外观模式通过简化复杂系统的接口来提高易用性。 在React中,这可以通过自定义Hooks实现,它们封装和管理相关逻辑,简化组件的使用。
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
const useUsersManagement = () => {
const [users, setUsers] = useState([]);
const addUser = (user) => {
setUsers([...users, user]);
};
const deleteUser = (userId) => {
setUsers(users.filter((user) => user.id !== userId));
};
return { users, addUser, deleteUser };
};
const UsersTable = ({ users, onDelete }) => {
return (
<table>
<thead>
<tr>
<th>姓名</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td>{user.name}</td>
<td>
<button onClick={() => onDelete(user.id)}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
);
};
const Users = () => {
const { users, addUser, deleteUser } = useUsersManagement();
return (
<>
<button onClick={() => addUser({ name: `User_${Date.now()}`, id: Date.now() })}>
增加用户
</button>
<UsersTable users={users} onDelete={deleteUser} />
</>
);
};
ReactDOM.createRoot(document.getElementById("root")).render(<Users />);
下面是对提到的几个脚手架工具的简要说明和它们的比较,包括它们的适用场景:
1. Create-React-App (CRA)
create-react-app
是一个官方推荐的用于创建单页 React 应用程序的脚手架。它提供了一个现代的构建设置,无需配置。2. Dva
3. Umi
4. Create-React-Library
create-react-library
是一个用于创建和发布 React 组件和库的脚手架。它简化了配置和构建过程,使开发者能够专注于编写组件逻辑。5. Storybook
总结
react-router-dom
是 React 的一个库,它提供了在 React 应用程序中进行路由管理的功能。这个库专门为浏览器环境设计,提供了一系列组件和钩子(hooks),以支持在 Web 应用程序中的路由导航和渲染。以下是 react-router-dom
的一些关键特性和组件:
主要特性
<Route>
和 <Link>
),可以更容易地管理和构建路由。history
对象进行页面跳转。核心组件
pushState
, replaceState
和 popstate
事件)来保持 UI 和 URL 的同步。<Link>
组件将不重新加载页面,而是触发历史记录的更改。react-router-dom
v5 中):用于渲染第一个与当前位置匹配的 <Route>
或 <Redirect>
。在 v6 中,此组件被 Routes
替代。使用场景
react-router-dom
提供了管理路由和视图的有效方式,无需重新加载页面。react-router-dom
提供了灵活的解决方案。版本注意
<Switch>
,引入了 <Routes>
,并更改了一些 API。在使用时,需要注意版本差异。总的来说,react-router-dom
是 React 应用中进行路由管理的强大工具,它通过声明式路由和组件化的方式,使得构建复杂的路由系统变得更简单、更直观。
React 应用中有多种样式解决方案,每种方案都有其独特的特点和最适合的使用场景。下面是一些常见的样式解决方案及其比较:
1. CSS Stylesheets
2. Inline Styles
3. CSS Modules
4. Styled Components
5. Emotion
6. Tailwind CSS
总结
React 生态系统中有许多组件库,每个库都有其独特的特点和适用场景。以下是一些流行的 React 组件库及其比较:
1. Material-UI
2. Ant Design
3. Bootstrap React
4. React Bootstrap
5. Semantic UI React
6. Chakra UI
7. Blueprint UI
**总结
选择组件库时,应考虑项目的需求、设计偏好、以及开发团队的熟悉度。
react-window
更轻量,而 react-virtualized
提供更多功能。react-window
适用于简单列表,react-virtualized
适用于更复杂的需求。react-dnd
用于实现复杂的拖放界面,react-draggable
专注于基本的拖动功能。react-dnd
适用于需要复杂拖放逻辑的应用,而 react-draggable
适用于简单拖动功能的场景。React 状态管理库提供了不同的方式来管理和维护应用的状态。每个库都有其独特的特点和最适合的使用场景。下面是一些主流的 React 状态管理库及其比较:
1. Redux
2. @reduxjs/toolkit
createSlice
和 createAsyncThunk
。3. MobX
4. Jotai
5. Valtio
6. Zustand
7. Recoil
总结
构建 React 应用时,有几种常用的构建工具,每种工具都有自己的特点和最适合的使用场景。下面是一些主流的构建工具,包括 Webpack、Rollup 和 esbuild 的对比:
1. Webpack
2. Rollup
3. esbuild
总结
选择合适的构建工具取决于项目的特定需求,如构建速度、包的大小、以及对复杂资源处理的需求。随着项目的发展和技术的迭代,这些工具的选择和使用方式也可能随之变化。
1. ESLint
2. Prettier
3. Stylelint
4. CommitLint
5. EditorConfig
.editorconfig
文件设置风格规则。总结
在 React 项目中,测试是保证应用质量的关键环节。以下是一些主要的测试工具,它们各自的特点、适应场景以及它们之间的关系:
1. Jest
2. Enzyme
3. React Testing Library
4. React Hooks Testing Library
关系
发布 React 项目涉及将开发的应用部署到服务器上,使其可供用户访问。这个过程通常包括打包、优化、上传文件到服务器等步骤。以下是一些常用的发布方式和工具,以及它们的特点和适应场景:
1. 手动部署
2. Netlify
3. Vercel
4. Docker 容器化
5. CI/CD 流程(如 GitHub Actions、GitLab CI/CD)
总结
在 React 中,"bailout" 是指在某些情况下,React 可以跳过不必要的组件更新来提高性能。这是一种避免不必要的渲染和相关工作的机制,有助于提高应用程序的效率。下面是几种常见的组件更新时的 bailout 情况:
1. 使用 React.memo
或 React.PureComponent
React.memo
: 用于函数组件。它将组件的渲染输出与其 props 进行浅比较,如果 props 没有改变,则不会重新渲染组件。React.PureComponent
: 类似于 React.memo
,但用于类组件。它通过浅比较 props
和 state
来避免不必要的更新。2. shouldComponentUpdate
方法
shouldComponentUpdate
生命周期方法来控制其更新行为。如果此方法返回 false
,则组件及其子组件不会更新。3. 返回相同的 state
或 props
state
或 props
在 setState
、useReducer
或 context
更新后保持不变(即,新的 state
或 props
与旧的完全相同),React 将跳过该组件及其子组件的重新渲染。4. 使用 React.memo
的自定义比较函数
React.memo
提供第二个参数(一个自定义比较函数),你可以更精确地控制何时更新组件。如果此函数返回 true
,组件更新将被跳过。5. 使用 Hooks 的稳定性
useMemo
和 useCallback
可以确保依赖项未改变时,对象或函数保持引用相同,这有助于防止子组件不必要的重新渲染。重要注意事项
React.memo
和 React.PureComponent
仅进行浅比较。如果 props
或 state
中含有复杂数据结构,可能需要更细致的比较策略。shouldComponentUpdate
中的复杂比较)可能大于简单地重新渲染组件。import React from "react";
import ReactDOM from "react-dom/client";
class ChildComponent extends React.Component {
state = {count: 0,};
render() {
console.log("ChildComponent render");
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Count: {this.state.count}
</button>
);
}
}
class ParentComponent extends React.PureComponent {
render() {
console.log("ParentComponent render");
return <ChildComponent/>;
}
}
class App extends React.Component {
render() {
console.log("App render");
return <ParentComponent />;
}
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
immer
是一个在 JavaScript 和 TypeScript 中用于管理不可变状态的流行库。它主要用于应用程序中状态管理的场景,特别是在 React 和 Redux 等库中。下面是 immer
的一些关键特点和使用方式:
核心概念
不可变性(Immutability): immer
旨在帮助维护不可变的状态。在 JavaScript 中,不可变性是一种确保数据结构不被改变的实践。这对于避免副作用、编写可预测的代码以及优化性能(特别是在 React 中)非常重要。
草稿(Draft)状态: 当你使用 immer
时,你实际上是在操作一个草稿状态。这意味着你可以像修改普通 JavaScript 对象一样修改这个草稿状态,而不用担心直接更改原始状态。
生产(Produce): immer
提供了一个名为 produce
的函数。这个函数接受当前状态(可以是任何类型的数据)和一个修改这个状态的函数。在这个函数内部,你可以“自由”修改传递的状态,而 immer
会在内部处理这些修改,最终生成一个新的不可变状态。
使用方式
基本用法: 在基本用法中,你将当前状态和一个修改器函数传递给 produce
。在修改器函数中,你可以直接修改状态,而 immer
将负责从这些修改中生成一个新的不可变状态。
import produce from "immer";
let baseState = [{ title: "Learn immer", done: false }];
let nextState = produce(baseState, draftState => {
draftState.push({ title: "Tweet about it" });
draftState[0].done = true;
});
在 React 中使用: 在 React 组件中,你可以使用 immer
来更方便地更新状态,特别是当状态对象较为复杂时。
this.setState(produce(draft => {
draft.some.deep.property = newValue;
}));
优势
immer
通过允许你编写看似可变的代码来简化这一过程,而实际上生成的是不可变的更新。immer
使用内部优化来确保性能,尤其是当处理大型或复杂的状态时。注意事项
immer
看起来允许你“修改”状态,但实际上你仍然在操作不可变的数据。这是通过在背后创建草稿状态和应用更改来实现的。immer
的性能是足够好的,但在极端情况下(如非常大的状态树),应该注意其性能影响。reselect
是一个用于 Redux 的选择器库,主要用于创建可记忆(memoized)的选择器。在 Redux 和类似的状态管理库中,选择器是用于从状态树中派生数据的函数。reselect
的主要目的是提高性能和可组合性,特别是在处理复杂的状态和计算密集型派生数据时。
关键特性
可记忆化(Memoization): reselect
的核心特性是它可以记忆选择器的计算结果。当你用相同的参数多次调用一个选择器时,如果这些参数自上次调用以来没有改变,reselect
会返回上一次的计算结果,而不是重新计算。
组合选择器: reselect
允许你将多个选择器组合成一个新的选择器。这是通过将其他选择器作为输入传递给 createSelector
函数来实现的。
使用场景
reselect
通过避免不必要的重复计算来优化性能。reselect
使得这个过程更加高效和简洁。基本用法
下面是一个 reselect
的基本使用例子:
import { createSelector } from 'reselect';
// 假设你有一个 Redux 状态,它包含一个用户列表
const getUsers = state => state.users;
// 使用 createSelector 创建一个可记忆的选择器
const getActiveUsers = createSelector(
[getUsers],
users => users.filter(user => user.isActive)
);
// 现在,getActiveUsers 只有在 users 发生改变时才会重新计算
注意事项
reselect
能正确地记忆计算结果,确保传递给选择器的参数在每次渲染时保持不变是很重要的。reselect
可能是过度优化。如果你的选择器非常简单,并且计算开销不大,那么添加 reselect
可能不会带来显著的性能改进。综上所述,reselect
是一个在处理复杂 Redux 应用中非常有用的库,它通过可记忆化选择器来提高性能,并使状态派生逻辑更加清晰和维护。