React 的设计思想围绕着几个核心理念展开,这些理念指导了 React 的架构设计、开发模式以及生态系统的构建。以下是 React 的设计思想和核心理念:
React 的核心思想之一是组件化,即将用户界面分解成独立、可复用的组件,每个组件封装了自己的结构、样式和行为。组件之间可以嵌套、组合,构建复杂的用户界面。
在 React 中,数据通过组件树以单向的方式流动,父组件将数据通过 props
传递给子组件,子组件通过回调函数将事件或数据传回给父组件。这种单向数据流的模式使得数据流动更加可控、可预测。
React 提倡声明式编程,开发者只需描述组件应该“呈现什么”(UI的最终状态),而不需要描述“如何”去执行(操作 DOM 的步骤)。React 会根据状态的变化,自动高效地更新和渲染组件。
React 使用虚拟 DOM 技术,通过在内存中创建一个轻量级的 DOM 树副本,当组件状态变化时,React 会首先在虚拟 DOM 中进行比较(diffing),找到最小的变化集(reconciliation),然后再将这些变化应用到真实 DOM 中。
React 强调组件的单一职责,即每个组件应只负责一件事情。通过将复杂的界面拆分成多个职责单一的小组件,React 提高了代码的可读性和可维护性。
React 组件具有生命周期方法,这些方法在组件的创建、更新和销毁过程中自动触发。通过生命周期方法,开发者可以在特定的阶段执行代码,如在组件挂载时加载数据,或在组件更新时处理副作用。
JSX 是 React 中使用的一种语法扩展,使得在 JavaScript 中编写 HTML 标签成为可能,提供了一种更加简洁和直观的方式来构建用户界面。
JavaScript 是浏览器原生支持的脚本语言,JSX 最终会被编译为标准的 JavaScript,以供浏览器执行。
JSX 的引入简化了 React 组件的开发过程,使得代码更加可读、易于维护。
只要使用了jsx,就要引用react:
javascriptimport React from "react"
在 React 中,自定义组件的首字母需要大写,这是因为 React 使用大写字母来区分用户定义的组件和原生的 HTML 标签。这个约定在 React 内部的渲染机制中起到了重要的作用。
区分原生 HTML 标签和自定义组件:
<div>
、<span>
等)都是小写的。而自定义组件通常是用 JavaScript 函数或类定义的,这些组件在 JSX 中使用时,React 会根据标签的首字母来判断它是一个原生 HTML 元素还是一个自定义组件。示例:
jsxfunction MyComponent() {
return <div>Hello, World!</div>;
}
const element = <MyComponent />; // React 将识别为自定义组件
const anotherElement = <mycomponent />; // React 会将其识别为一个未知的 HTML 标签,可能导致渲染错误
JSX 语法的设计决定:
React.createElement(ComponentName)
,而小写字母开头的元素则会被编译为 React.createElement('div')
这样的原生 HTML 标签。编译后的代码:
jsx<MyComponent />
编译后类似于:
javascriptReact.createElement(MyComponent);
而:
jsx<mycomponent />
编译后类似于:
javascriptReact.createElement('mycomponent');
后者会导致 React 尝试渲染一个名为 mycomponent
的 HTML 标签,但这是不存在的,可能会导致渲染出一个空的 HTML 元素。
遵循 JavaScript 类和构造函数命名的惯例:
React 使用虚拟 DOM 来表示用户界面的结构。虚拟 DOM 是以树结构来表示的,每个组件在渲染时都会生成一个虚拟 DOM 树。
如果一个组件返回多个根元素,React 将无法确定它们的父子关系,从而无法正确构建虚拟 DOM 树。这会导致渲染逻辑的复杂化,增加 React 内部处理的难度。
React 的组件生命周期指的是组件从创建、更新到销毁的整个过程。React 提供了一系列的生命周期方法(或钩子函数),允许开发者在组件的不同阶段执行特定的代码逻辑。这些生命周期方法可以分为三个主要阶段:挂载阶段、更新阶段、卸载阶段。
挂载阶段是组件实例被创建并插入到 DOM 中的过程。在这个阶段,会依次调用以下生命周期方法:
constructor(props)
:
constructor
,React 会默认调用它。jsxconstructor(props) {
super(props);
this.state = {
count: 0,
};
}
static getDerivedStateFromProps(props, state)
:
props
时调用,并在 render
方法之前调用。它用于根据 props
更新状态。props
中派生出新的状态时使用。jsxstatic getDerivedStateFromProps(props, state) {
if (props.someValue !== state.someValue) {
return { someValue: props.someValue };
}
return null; // 没有状态更新则返回 null
}
render()
:
render
方法应该是纯函数,不应包含副作用。jsxrender() {
return <div>Hello, World!</div>;
}
componentDidMount()
:
jsxcomponentDidMount() {
// 例如:数据请求
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data }));
}
更新阶段发生在组件的 props
或 state
发生变化时。此时,组件会重新渲染,并依次调用以下生命周期方法:
static getDerivedStateFromProps(props, state)
:
props
或状态变化时调用,用于更新状态。shouldComponentUpdate(nextProps, nextState)
:
props
或 state
时调用,返回值为 true
或 false
,用来决定组件是否需要更新。可以通过该方法优化性能,避免不必要的渲染。jsxshouldComponentUpdate(nextProps, nextState) {
// 只有当 props 或 state 发生实际变化时才更新
return nextProps.someValue !== this.props.someValue;
}
render()
:
render
方法用于渲染组件的 UI。每次更新时都会调用此方法。getSnapshotBeforeUpdate(prevProps, prevState)
:
componentDidUpdate
方法。jsxgetSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
return this.listRef.scrollHeight; // 获取更新前的滚动高度
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot)
:
snapshot
参数是 getSnapshotBeforeUpdate
方法的返回值。jsxcomponentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
this.listRef.scrollTop += snapshot; // 根据之前的快照值调整滚动位置
}
}
卸载阶段是在组件即将从 DOM 中移除时调用的,这个阶段只有一个生命周期方法:
componentWillUnmount()
:
componentDidMount
中创建的资源。jsxcomponentWillUnmount() {
clearInterval(this.interval);
}
React 16 引入了新的生命周期方法,用于处理组件中的错误:
static getDerivedStateFromError(error)
:
jsxstatic getDerivedStateFromError(error) {
return { hasError: true }; // 更新状态,显示错误界面
}
componentDidCatch(error, info)
:
jsxcomponentDidCatch(error, info) {
// 可以将错误信息发送到日志服务
logErrorToService(error, info);
}
React 的生命周期方法为开发者提供了在组件的不同阶段执行特定操作的机会。理解和合理使用这些生命周期方法,可以帮助开发者更好地管理组件的状态、优化性能,并在组件的整个生命周期内有效地处理各种任务。
constructor
、getDerivedStateFromProps
、render
、componentDidMount
getDerivedStateFromProps
、shouldComponentUpdate
、render
、getSnapshotBeforeUpdate
、componentDidUpdate
componentWillUnmount
getDerivedStateFromError
、componentDidCatch
React 的事件机制与传统的 HTML DOM 事件处理有一些不同,它引入了一种名为 合成事件(Synthetic Event) 的机制。这种机制在跨浏览器兼容性、性能优化以及事件处理的一致性方面提供了许多好处。
合成事件(Synthetic Event):
事件委托(Event Delegation):
自动事件绑定:
onClick
、onChange
等。React 会自动将这些事件处理程序绑定到合适的元素上。onClick
、onMouseEnter
等,而不是传统 HTML 中的 onclick
或 onmouseover
等。示例:
jsxfunction MyButton() {
function handleClick() {
console.log('Button clicked');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
事件传播与阻止:
stopPropagation()
方法来阻止事件传播。示例:
jsxfunction handleClick(event) {
event.stopPropagation(); // 阻止事件冒泡
console.log('Button clicked');
}
事件的回收与优化:
event.persist()
方法来保留事件对象。示例:
jsxfunction handleClick(event) {
event.persist(); // 防止事件对象被回收
setTimeout(() => {
console.log(event.type); // 此时依然可以访问事件对象的属性
}, 1000);
}
命名方式:
onClick
、onChange
,而原生 HTML 事件处理程序使用全小写的命名方式,如 onclick
、onchange
。事件绑定的方式:
事件处理中的 this
:
this
默认不会绑定到组件实例上,需要手动绑定或使用箭头函数。示例(类组件):
jsxclass MyButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Button clicked', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
示例(函数组件):
jsxfunction MyButton() {
const handleClick = () => {
console.log('Button clicked');
};
return (
<button onClick={handleClick}>
Click me
</button>
);
}
跨浏览器一致性:
让子组件渲染在除了父组件之外的dom节点的方式。通常情况下,React 组件会渲染在它们在组件树中的位置对应的 DOM 结构中,但是 Portal 提供了一种将子元素渲染到不同 DOM 子树中的能力,而不改变组件的层级结构。
javascriptReactDom.createPortal(child, container)
常用于弹框、提示框。
Fragment
是 React 提供的一种包裹组件的方式,用于将多个元素组合在一起,而不会在 DOM 中增加额外的节点。通常情况下,React 组件需要返回一个单一的根元素,而 Fragment
允许组件返回多个元素,而不需要额外的包裹 div
或其他元素。
Fragment
可以避免在 DOM 中生成额外的节点,例如不需要的 div
包裹层。Fragment
可以帮助保持更简洁的 DOM 结构,有助于提高页面渲染性能。有两种方式使用 Fragment
:
标准的 Fragment
语法:
jsximport React, { Fragment } from 'react';
function MyComponent() {
return (
<Fragment>
<h1>Title</h1>
<p>This is a paragraph.</p>
</Fragment>
);
}
简写语法(空标签):
jsxfunction MyComponent() {
return (
<>
<h1>Title</h1>
<p>This is a paragraph.</p>
</>
);
}
Context
是 React 提供的一种解决组件间共享数据(比如全局状态)的机制,而不需要通过逐层传递 props
。Context
可以帮助你在组件树的深层嵌套中传递数据,而不必手动通过每一级组件的 props
传递。
Context
通常用于在整个应用中共享一些全局数据,比如当前用户信息、主题设置、语言环境等。props
钻取”:当一个组件需要从其上层组件获取数据,而这个数据并非直接与其父组件相关时,Context
可以避免一层层地通过 props
传递数据。创建 Context:
React.createContext()
创建一个 Context
对象。jsxconst MyContext = React.createContext(defaultValue);
提供数据(Provider):
Context.Provider
包裹组件树,并通过 value
属性传递数据。所有包裹在 Provider
内部的组件都可以访问这个数据。jsx<MyContext.Provider value={/* some value */}>
<MyComponent />
</MyContext.Provider>
消费数据(Consumer):
Context.Consumer
或者 useContext
钩子来获取 Context
中的数据。使用 Consumer
:
jsx<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
使用 useContext
钩子:
jsxconst value = React.useContext(MyContext);
jsx// 1. 创建 Context
const ThemeContext = React.createContext('light');
function App() {
// 2. 使用 Provider 提供数据
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// 3. 使用 useContext 消费数据
const theme = React.useContext(ThemeContext);
return <button className={theme}>I am styled by theme context!</button>;
}
Context
用于在 React 应用中全局共享数据,避免“props
钻取”。Provider
提供数据,使用 Consumer
或 useContext
钩子来消费数据。React Router 是 React 的标准路由库,用于在单页应用(SPA)中实现客户端路由。它允许开发者在 React 应用中定义和管理不同的页面或视图,并根据 URL 的变化来渲染相应的组件,而无需重新加载整个页面。
Router(路由器):
BrowserRouter
和 HashRouter
是两种常用的路由器组件。BrowserRouter
使用 HTML5 的 history
API 实现路由,URL 形式为标准的路径结构。HashRouter
使用 URL 的哈希部分(#
)来管理路由,URL 形式为 http://example.com/#/path
。Route(路由):
Route
组件用于定义 URL 路径与 React 组件之间的映射关系。当 URL 与某个 Route
的 path
属性匹配时,React Router 会渲染该路径对应的组件。jsx<Route path="/home" component={HomeComponent} />
Link(链接):
Link
组件用于创建可点击的导航链接,类似于 HTML 的 <a>
标签,但它不会触发页面刷新,而是使用 React Router 的路由机制来进行导航。jsx<Link to="/home">Go to Home</Link>
Switch(切换):
Switch
组件用于确保只渲染一个匹配的 Route
。当多个 Route
的 path
属性与当前 URL 匹配时,Switch
只会渲染第一个匹配的 Route
。jsx<Switch>
<Route path="/about" component={AboutComponent} />
<Route path="/home" component={HomeComponent} />
</Switch>
Redirect(重定向):
Redirect
组件用于将用户从一个路径重定向到另一个路径。jsx<Redirect from="/old-path" to="/new-path" />
useParams、useLocation、useHistory:
jsxconst { id } = useParams(); // 获取URL中的参数
const location = useLocation(); // 获取当前路径信息
const history = useHistory(); // 访问浏览历史,用于导航
jsximport React from 'react';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
function Home() {
return <h2>Home Page</h2>;
}
function About() {
return <h2>About Page</h2>;
}
function App() {
return (
<Router>
<nav>
<Link to="/home">Home</Link>
<Link to="/about">About</Link>
</nav>
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/" exact>
<h2>Welcome to the App</h2>
</Route>
</Switch>
</Router>
);
}
export default App;
在这个示例中:
Router
包裹了整个应用,管理着应用的路由行为。Link
创建了导航链接,用于在不同页面之间切换。Route
定义了路径与组件的映射关系。Switch
确保在一个时刻只渲染一个路径匹配的组件。Route
、Link
、Switch
等组件来实现路由管理和页面切换。React Hooks 是 React 16.8 引入的一组新特性,它允许你在函数组件中使用状态和其他 React 特性,而不需要编写类组件。Hooks 提升了函数组件的功能,使其能够处理状态、生命周期、上下文等高级功能,从而简化了组件的编写和复用。
useState
:
示例:
jsximport React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // 初始化状态为 0
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
useEffect
:
useEffect
可以看作是 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的组合。示例:
jsximport React, { useEffect } from 'react';
function Example() {
useEffect(() => {
document.title = "Component Mounted";
return () => {
document.title = "Component Unmounted"; // 清理操作
};
}, []); // 空数组表示仅在组件挂载和卸载时执行
return <div>Check the document title!</div>;
}
useContext
:
props
逐层传递数据的过程。useContext
,组件可以直接访问上下文数据。示例:
jsximport React, { useContext } from 'react';
const ThemeContext = React.createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Button with {theme} theme</button>;
}
useReducer
:
useState
的替代方案。useReducer
接受一个 reducer 函数和一个初始状态,返回当前状态和一个 dispatch 函数。示例:
jsximport React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
useRef
:
示例:
jsximport React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus(); // 使用 useRef 访问 DOM 元素
};
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</div>
);
}
本文作者:Jeff Wu
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!