Appearance
React 核心概念
本文介绍React的核心概念,包括组件、Props、State、生命周期、事件处理等基础知识点。
什么是React?
React是一个用于构建用户界面的JavaScript库,由Facebook开发并维护。它主要用于构建单页应用程序(SPA),但也可以用于开发移动应用和其他平台。
React的特点
- 声明式编程:React使用声明式范式,让你可以轻松描述应用的最终状态,而不必关心状态转换的具体过程。
- 基于组件:构建封装的、可管理自身状态的组件,然后组合它们以构建复杂的UI。
- 虚拟DOM:React维护一个内存中的虚拟DOM,通过比较(diffing)算法高效更新实际DOM。
- 单向数据流:数据从父组件流向子组件,使应用中的数据流动可预测。
- JSX语法:JavaScript的语法扩展,允许在JavaScript中编写类似HTML的代码。
组件
组件是React应用的核心构建块,它们是可重用的、独立的代码片段,返回要在页面上渲染的React元素。
函数组件
jsx
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 使用箭头函数
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
// 简化版本
const Welcome = props => <h1>Hello, {props.name}</h1>;类组件
jsx
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}组件组合
jsx
function App() {
return (
<div>
<Welcome name="Alice" />
<Welcome name="Bob" />
<Welcome name="Charlie" />
</div>
);
}JSX
JSX是JavaScript的语法扩展,它看起来像HTML,但具有JavaScript的全部功能。
jsx
const element = <h1>Hello, world!</h1>;
// 使用JavaScript表达式
const name = "John";
const element = <h1>Hello, {name}!</h1>;
// 属性
const element = <img src={user.avatarUrl} alt="User avatar" />;
// 子元素
const element = (
<div>
<h1>Title</h1>
<p>Paragraph</p>
</div>
);JSX防注入攻击
React DOM在渲染所有输入内容之前,默认会进行转义,有助于防止XSS(跨站脚本)攻击。
jsx
const title = "<script>alert('XSS');</script>";
// 这是安全的,脚本不会执行
const element = <h1>{title}</h1>;JSX表示对象
Babel会将JSX编译为React.createElement()调用。
jsx
// 这两个例子是等价的
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);Props
Props(属性)是从父组件传递到子组件的只读数据。
jsx
// 父组件传递props
function App() {
return <Welcome name="Sara" age={25} isAdmin={true} />;
}
// 子组件接收props
function Welcome(props) {
return (
<div>
<h1>Hello, {props.name}</h1>
<p>Age: {props.age}</p>
{props.isAdmin && <p>Admin user</p>}
</div>
);
}Props的特点
- Props是只读的,组件不应修改自己的props
- 所有React组件必须像纯函数一样保护它们的props不被修改
默认Props
jsx
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Welcome.defaultProps = {
name: 'Guest'
};
// 类组件中
class Welcome extends React.Component {
static defaultProps = {
name: 'Guest'
};
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}Props类型检查
使用PropTypes库进行类型检查:
jsx
import PropTypes from 'prop-types';
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Welcome.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isAdmin: PropTypes.bool
};State
State是组件内部管理的可变数据,当state改变时,组件会重新渲染。
类组件中的State
jsx
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}函数组件中的State (使用Hooks)
jsx
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}State更新可能是异步的
jsx
// 错误方式:可能导致更新丢失
this.setState({
count: this.state.count + 1
});
// 正确方式:使用函数形式
this.setState((prevState) => ({
count: prevState.count + 1
}));State合并更新
jsx
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
email: ''
};
}
handleNameChange = (e) => {
// 只更新name,email保持不变
this.setState({ name: e.target.value });
}
render() {
return (
<form>
<input
type="text"
value={this.state.name}
onChange={this.handleNameChange}
/>
</form>
);
}
}生命周期
类组件有多个生命周期方法,让你在组件的不同阶段执行代码。
挂载阶段
jsx
class LifecycleDemo extends React.Component {
constructor(props) {
super(props);
this.state = { data: null };
console.log('1. Constructor');
}
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps');
return null; // 返回null表示不更新state
}
componentDidMount() {
console.log('4. componentDidMount');
// 适合进行API调用、订阅事件等
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
console.log('3. render');
return <div>Lifecycle Demo</div>;
}
}更新阶段
jsx
class LifecycleDemo extends React.Component {
// ... 挂载阶段方法 ...
shouldComponentUpdate(nextProps, nextState) {
console.log('5. shouldComponentUpdate');
// 返回true允许更新,返回false阻止更新
return true;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('7. getSnapshotBeforeUpdate');
// 在DOM更新之前捕获一些信息(如滚动位置)
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('8. componentDidUpdate');
// 组件更新后执行,可以进行DOM操作或网络请求
}
render() {
console.log('6. render');
return <div>Lifecycle Demo</div>;
}
}卸载阶段
jsx
class LifecycleDemo extends React.Component {
// ... 其他生命周期方法 ...
componentWillUnmount() {
console.log('9. componentWillUnmount');
// 清理工作:取消网络请求、清除定时器、取消订阅等
}
}错误处理
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新state使下一次渲染显示降级UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 记录错误信息
console.error('Error:', error);
console.error('Error Info:', errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 使用错误边界
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>事件处理
React事件使用驼峰命名法,并传递函数作为事件处理器。
jsx
// 函数组件中的事件处理
function Button() {
const handleClick = (e) => {
e.preventDefault(); // 阻止默认行为
console.log('Button clicked');
};
return <button onClick={handleClick}>Click me</button>;
}
// 类组件中的事件处理
class Button extends React.Component {
handleClick = (e) => {
e.preventDefault();
console.log('Button clicked');
}
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}绑定this
在类组件中,需要确保事件处理器中的this正确绑定。
jsx
class Button extends React.Component {
constructor(props) {
super(props);
// 方法1:在构造函数中绑定
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('this is:', this);
}
// 方法2:使用箭头函数类属性(需要Babel支持)
handleClickArrow = () => {
console.log('this is:', this);
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click me (bound in constructor)</button>
<button onClick={this.handleClickArrow}>Click me (arrow function)</button>
{/* 方法3:在回调中使用箭头函数(每次渲染都会创建新函数,可能影响性能) */}
<button onClick={() => this.handleClick()}>Click me (inline arrow)</button>
</div>
);
}
}向事件处理器传递参数
jsx
// 方法1:使用箭头函数
<button onClick={(e) => this.handleClick(id, e)}>Delete</button>
// 方法2:使用bind(事件对象e会被自动作为最后一个参数传入)
<button onClick={this.handleClick.bind(this, id)}>Delete</button>条件渲染
根据应用状态有条件地渲染UI部分。
jsx
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
// 方法1:使用if语句
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
// 方法2:使用三元运算符
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
return isLoggedIn ? <UserGreeting /> : <GuestGreeting />;
}
// 方法3:使用逻辑与运算符(短路求值)
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>You have {unreadMessages.length} unread messages.</h2>
}
</div>
);
}阻止组件渲染
jsx
function WarningBanner(props) {
if (!props.warn) {
return null; // 不渲染任何内容
}
return (
<div className="warning">
Warning!
</div>
);
}列表渲染
使用JavaScript的map()方法渲染多个组件。
jsx
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return <ul>{listItems}</ul>;
}
// 内联map
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
)}
</ul>
);
}键(Keys)
键帮助React识别哪些项目已更改、添加或删除。
jsx
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
// 如果没有稳定ID,可以使用索引作为键(不推荐)
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
);表单
在React中,表单元素通常由React组件控制。
受控组件
jsx
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
handleChange = (event) => {
this.setState({value: event.target.value});
}
handleSubmit = (event) => {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}处理多个输入
jsx
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
}
handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value // 使用ES6计算属性名语法
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange}
/>
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange}
/>
</label>
</form>
);
}
}非受控组件
使用ref获取表单值,而不是通过state控制。
jsx
class FileInput extends React.Component {
constructor(props) {
super(props);
this.fileInput = React.createRef();
}
handleSubmit = (event) => {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}组合 vs 继承
React推荐使用组合而非继承来复用组件之间的代码。
包含关系
jsx
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">Welcome</h1>
<p className="Dialog-message">Thank you for visiting our spacecraft!</p>
</FancyBorder>
);
}特殊情况
jsx
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={<Contacts />}
right={<Chat />}
/>
);
}Context
Context提供了一种在组件树中共享值的方式,而不必显式地通过每个层级传递props。
jsx
// 创建Context
const ThemeContext = React.createContext('light');
// 提供Context
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 中间组件不需要传递theme prop
function Toolbar() {
return <ThemedButton />;
}
// 消费Context(类组件)
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
// 消费Context(函数组件)
function ThemedButton() {
return (
<ThemeContext.Consumer>
{value => <Button theme={value} />}
</ThemeContext.Consumer>
);
}
// 使用useContext Hook
function ThemedButton() {
const theme = React.useContext(ThemeContext);
return <Button theme={theme} />;
}代码分割
代码分割帮助你"懒加载"当前用户所需的内容,提高应用性能。
jsx
// 使用React.lazy和Suspense
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
// 基于路由的代码分割
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
}错误边界
错误边界是React组件,它可以捕获子组件树中的JavaScript错误,记录错误并显示备用UI。
jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新state使下一次渲染显示降级UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级UI并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 使用方式
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>Refs
Refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素。
jsx
// 创建Refs(类组件)
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
// 访问DOM节点
this.myRef.current.focus();
}
render() {
return <input ref={this.myRef} />;
}
}
// 使用useRef Hook(函数组件)
function MyComponent() {
const myRef = React.useRef(null);
React.useEffect(() => {
// 访问DOM节点
myRef.current.focus();
}, []);
return <input ref={myRef} />;
}
// 回调Refs
function CustomTextInput(props) {
let textInput = null;
function handleClick() {
textInput.focus();
}
return (
<div>
<input
type="text"
ref={(element) => { textInput = element; }}
/>
<button onClick={handleClick}>Focus the text input</button>
</div>
);
}高阶组件 (HOC)
高阶组件是一个函数,它接受一个组件并返回一个新组件。
jsx
// 高阶组件示例
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange = () => {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// 传递props和注入的数据
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
// 使用HOC
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
const BlogPostWithSubscription = withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id)
);Render Props
Render Props是一种在React组件之间共享代码的技术,它使用一个值为函数的prop来传递需要动态渲染的内容。
jsx
class Mouse extends React.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}>
{/* 调用render prop,传入state作为参数 */}
{this.props.render(this.state)}
</div>
);
}
}
// 使用render prop
function App() {
return (
<Mouse render={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)} />
);
}
// 使用children作为函数
function App() {
return (
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
);
}Portals
Portals提供了一种将子节点渲染到存在于父组件以外的DOM节点的方式。
jsx
import ReactDOM from 'react-dom';
function Modal(props) {
return ReactDOM.createPortal(
props.children,
document.getElementById('modal-root')
);
}
function App() {
return (
<div>
<h1>App</h1>
<Modal>
<h2>Modal Content</h2>
<p>This is rendered outside the parent component's DOM hierarchy.</p>
</Modal>
</div>
);
}Fragments
Fragments允许你将子元素列表添加到一个分组中,而无需向DOM添加额外节点。
jsx
function Table() {
return (
<table>
<tbody>
<tr>
<Columns />
</tr>
</tbody>
</table>
);
}
function Columns() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
// 短语法
function Columns() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}严格模式
StrictMode是一个用来突出显示应用程序中潜在问题的工具。
jsx
import React from 'react';
function App() {
return (
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
);
}严格模式检查:
- 识别不安全的生命周期方法
- 关于使用过时字符串ref API的警告
- 关于使用废弃的findDOMNode方法的警告
- 检测意外的副作用
- 检测过时的context API