React教程(React入门教程)(React组件、JSX、React Props、React State、React事件处理、Hooks、高阶组件HOC)
文章目录
- 一、什么是React
- 二、React的特点
- 1. **组件化**:React采用组件化模式,将UI拆分为独立、可复用的组件,提高代码复用率。
- 2. **声明式设计**:React采用声明范式,可以轻松描述应用状态与UI之间的关系。
- 3. **虚拟DOM**:React不直接操作DOM,而是通过虚拟DOM和diff算法,以最小的步骤作用到真实的DOM上,提升性能。
- 4. **单向数据流**:React实现了单向响应的数据流,从数据到视图的渲染,减少了重复代码,比传统数据绑定更简单。
- 5. **高效**:通过对DOM的模拟,最大限度地减少与DOM的交互。
- 6. **灵活**:React可以与已知的库或框架很好地配合。
- 三、React与其他框架的比较
- - React只专注于MVC框架中的V(视图层),而AngularJS是一个完整的MVC框架。
- - React是单向数据流,非双向数据绑定。
- - React不直接操作DOM,而是通过虚拟DOM进行编程。
- 四、React应用现状
- 五、环境搭建
- 六、React核心概念
- 1. 组件
- **类组件示例**:
- **函数组件示例**:
- 2. JSX
- 3. Props
- 4. State
- 5. 事件处理
- React使用事件处理函数处理用户交互。
- 疑问:`this.handleClick = this.handleClick.bind(this);`多余吗?
- 为什么需要绑定?
- 为什么这行代码是必要的?
- 对比其他写法(为什么这行代码是正确方式)
- ❌ 错误写法(不绑定):
- ✅ 正确写法(构造函数中绑定):
- ✅ 替代方案(类属性 + 箭头函数,ES7 语法):
- 为什么说“不多余”?
- 为什么有人觉得“多余”?
- 结论
- 疑问:为什么 this 会丢失?
- 详细解释
- 1. 为什么 `this` 会丢失?(看不懂,尴尬😭测也测不出来。。。)
- 2. 为什么在构造函数中绑定是正确的方式?
- 3. 一个简单示例说明
- 为什么不是多余的?
- 6. Hooks
- 七、高阶组件
- 八、实战:创建一个简单的组件
- 九、学习资源推荐
- 1. **官方文档**:React官网提供了最权威的文档和教程。
- 2. **React视频教程**:《React极速入门指南》、《Vue、Angular、React 项目开发与深度对比》。
- 3. **实践项目**:通过构建实际项目(如电商网站、社交应用)来巩固知识。
- 4. **社区支持**:React社区活跃,Stack Overflow、GitHub等平台有大量问题解答。
- 十、结语
一、什么是React
React是由Facebook的软件工程师Jordan Walke创建的JavaScript库,于2011年部署于Facebook的newsfeed,2012年部署于Instagram,2013年5月正式开源。React专注于构建用户界面(UI),是MVC框架中的"V"(视图层),而非完整的MVC框架。
React的核心理念是"从UI出发,抽象出不同的组件,继而将它们拼装起来",这顺应了Web开发组件化的趋势。
二、React的特点
1. 组件化:React采用组件化模式,将UI拆分为独立、可复用的组件,提高代码复用率。
2. 声明式设计:React采用声明范式,可以轻松描述应用状态与UI之间的关系。
3. 虚拟DOM:React不直接操作DOM,而是通过虚拟DOM和diff算法,以最小的步骤作用到真实的DOM上,提升性能。
4. 单向数据流:React实现了单向响应的数据流,从数据到视图的渲染,减少了重复代码,比传统数据绑定更简单。
5. 高效:通过对DOM的模拟,最大限度地减少与DOM的交互。
6. 灵活:React可以与已知的库或框架很好地配合。
三、React与其他框架的比较
React与AngularJS等框架的主要区别在于:
- React只专注于MVC框架中的V(视图层),而AngularJS是一个完整的MVC框架。
- React是单向数据流,非双向数据绑定。
参考文章:React单向数据流(unidirectional data flow)与非双向数据绑定的理解
- React不直接操作DOM,而是通过虚拟DOM进行编程。
四、React应用现状
React在国外应用广泛,Facebook、Yahoo、Reddit等知名公司都在使用。在国内,知乎、豆瓣、优酷等大厂也逐渐采用React技术栈。
值得注意的是,截至2022年第一季度,国内前端框架使用上大多偏向于Vue,这导致React前端工程师相对稀缺。在学习难度上,React比Vue稍高,这也是许多企业对React开发经验要求较高的原因。
五、环境搭建
使用create-react-app脚手架快速搭建React项目:
# 创建新项目
npx create-react-app react-project# 进入项目目录
cd react-project# 启动项目
yarn start
如果提示没有yarn可看这里:yarn命令介绍(替代npm命令的JavaScript包管理工具)
六、React核心概念
1. 组件
React的核心是组件。组件是UI的构建块,可以是类组件或函数组件。
类组件示例:
import React, { Component } from 'react'; // 从React库中导入React对象和Component类class Welcome extends Component { // 定义一个名为Welcome的类组件,继承自React.Componentrender() { // render方法是React组件的核心,用于返回要渲染的UIreturn <h1>Hello, {this.props.name}</h1>; // 返回一个h1元素,显示"Hello,"和传入的name属性}
}
函数组件示例:
function Welcome(props) { // 定义一个名为Welcome的函数组件,接收props参数return <h1>Hello, {props.name}</h1>; // 返回一个h1元素,显示"Hello,"和传入的name属性
}
2. JSX
JSX是JavaScript语法的扩展,使HTML结构与JavaScript代码混合在一起,提高可读性。
const element = <h1>Hello, world!</h1>; // 创建一个h1元素,显示"Hello, world!"
3. Props
Props是组件的属性,用于从父组件向子组件传递数据。
function Welcome(props) { // 定义一个名为Welcome的函数组件,接收props参数return <h1>Hello, {props.name}</h1>; // 返回一个h1元素,显示"Hello,"和传入的name属性
}const element = <Welcome name="Sara" />; // 创建一个Welcome组件的实例,传入name属性为"Sara"
4. State
State是组件内部的状态,用于管理组件的动态数据。
class Counter extends Component { // 定义一个名为Counter的类组件,继承自React.Componentconstructor(props) { // 构造函数,初始化组件状态super(props); // 调用父类Component的构造函数this.state = { count: 0 }; // 初始化组件状态,count属性初始值为0}render() { // render方法,返回要渲染的UIreturn (<div><p>Count: {this.state.count}</p> // 显示count状态的值<button onClick={() => this.setState({ count: this.state.count + 1 })}>Increment // 按钮显示"Increment"</button></div>);}
}
5. 事件处理
React使用事件处理函数处理用户交互。
class Toggle extends Component { // 定义一个名为Toggle的类组件,继承自React.Componentconstructor(props) { // 构造函数,初始化组件状态super(props); // 调用父类Component的构造函数this.state = { isToggleOn: true }; // 初始化组件状态,isToggleOn属性初始值为true// 为确保this指向正确,绑定事件处理函数this.handleClick = this.handleClick.bind(this);}handleClick() { // 定义点击事件处理函数this.setState(prevState => ({ // 更新状态,使用prevState获取之前的状态isToggleOn: !prevState.isToggleOn // 将isToggleOn状态取反}));}render() { // render方法,返回要渲染的UIreturn (<button onClick={this.handleClick}> // 按钮的点击事件绑定到handleClick方法{this.state.isToggleOn ? 'ON' : 'OFF'} // 根据isToggleOn状态显示"ON"或"OFF"</button>);}
}
疑问:this.handleClick = this.handleClick.bind(this);
多余吗?
在 React 类组件中,this.handleClick = this.handleClick.bind(this);
不是多余的,而是必要的。以下是详细解释:
为什么需要绑定?
在 JavaScript 中,类方法默认不会自动绑定 this
。当我们将方法作为事件处理函数(如 onClick={this.handleClick}
)传递时:
- 事件触发时,
handleClick
会被调用,但this
的指向会丢失(指向undefined
或window
,取决于上下文)。 - 例如:
this.setState
会因this
为undefined
而报错。
为什么这行代码是必要的?
this.handleClick = this.handleClick.bind(this); // 绑定 this 指向组件实例
- 作用:确保
handleClick
方法中的this
指向组件实例(Toggle
的实例)。 - 如果不绑定:
this.setState
会失败(this
为undefined
)。
对比其他写法(为什么这行代码是正确方式)
❌ 错误写法(不绑定):
render() {return <button onClick={this.handleClick}>...</button>; // this.handleClick 中的 this 为 undefined
}
✅ 正确写法(构造函数中绑定):
constructor(props) {super(props);this.state = { isToggleOn: true };this.handleClick = this.handleClick.bind(this); // 必须在这里绑定
}
✅ 替代方案(类属性 + 箭头函数,ES7 语法):
class Toggle extends Component {handleClick = () => { // 自动绑定 thisthis.setState(...);};// ...其他代码
}
注意:这是 ES7 语法(类属性),不是类方法。如果使用普通方法(非箭头函数),必须手动绑定。
为什么说“不多余”?
- 性能:在构造函数中绑定一次(
this.handleClick = ...
)比在render
中每次渲染都绑定(onClick={this.handleClick.bind(this)}
)更高效。 - 正确性:确保
this
在组件生命周期中始终指向组件实例。
为什么有人觉得“多余”?
- 误解 ES6 类方法:误以为类方法会自动绑定
this
(实际不会)。 - 混淆箭头函数:如果写成
handleClick = () => { ... }
(类属性),则无需绑定,但原始代码是普通方法(handleClick() { ... }
),所以必须绑定。
结论
this.handleClick = this.handleClick.bind(this);
是必要的,不是多余的。
它确保了事件处理函数中的 this
指向组件实例,是 React 类组件的标准实践。
✅ 推荐写法(构造函数绑定):
constructor(props) {super(props);this.state = { isToggleOn: true };this.handleClick = this.handleClick.bind(this); // 必须写 }
✅ 替代写法(ES7 类属性):
handleClick = () => { // 自动绑定,无需 bindthis.setState(...); }
原始代码中的写法是正确且标准的,不是多余的。
疑问:为什么 this 会丢失?
在 JavaScript 中,this
的指向取决于函数的调用方式,而不是定义方式。这是理解这个问题的关键。
详细解释
1. 为什么 this
会丢失?(看不懂,尴尬😭测也测不出来。。。)
当我们在 React 中这样写:
<button onClick={this.handleClick}>Toggle</button>
React 实际上是这样调用的:
// 伪代码
button.addEventListener('click', this.handleClick);
当按钮被点击时,React 会调用 this.handleClick
,但调用方式是作为普通函数调用,而不是作为对象的方法调用。
在 JavaScript 中:
- 如果函数是作为对象的方法调用(
obj.method()
),this
指向obj
- 如果函数是作为普通函数调用(
method()
),this
指向全局对象(浏览器中是window
)或undefined
(严格模式下)
所以当 handleClick
通过 onClick
被调用时:
- 它不是通过
this.handleClick()
调用的 - 而是通过
handleClick()
调用的 - 因此
this
会丢失,指向undefined
或window
2. 为什么在构造函数中绑定是正确的方式?
constructor(props) {super(props);this.state = { isToggleOn: true };this.handleClick = this.handleClick.bind(this); // 绑定 this
}
这行代码的作用是:创建一个新的函数,将 this
指向组件实例。这样,无论 handleClick
如何被调用,this
都会指向组件实例。
3. 一个简单示例说明
class Example {constructor() {this.name = "Example";// 不绑定 thisthis.greet = this.greet.bind(this);}greet() {console.log("Hello, " + this.name);}run() {// 普通函数调用const fn = this.greet;fn(); // 输出: Hello, undefined (this 丢失)}
}const ex = new Example();
ex.run(); // 会输出 "Hello, undefined"
如果我们在构造函数中绑定了 this
:
constructor() {this.name = "Example";this.greet = this.greet.bind(this); // 绑定 this
}
那么 fn()
会正确输出 “Hello, Example”。
为什么不是多余的?
在 React 中,事件处理函数的 this
丢失是标准行为,不是 React 特有的问题。这是 JavaScript 语言本身的特性。
- 如果不绑定,
this.setState
会失败(因为this
是undefined
) - 在构造函数中绑定一次,比每次渲染都绑定(如
onClick={this.handleClick.bind(this)}
)更高效
所以,this.handleClick = this.handleClick.bind(this);
不是多余的,而是 React 类组件中必须的步骤,确保事件处理函数中的 this
指向组件实例。
6. Hooks
React Hooks是React 16.8引入的新特性,允许在函数组件中使用状态和其他React特性。
useState:
import React, { useState } from 'react'; // 从React库中导入useState Hookfunction Example() { // 定义一个函数组件Example// 声明一个名为"count"的state变量,初始值为0const [count, setCount] = useState(0);return (<div><p>You clicked {count} times</p> // 显示点击次数<button onClick={() => setCount(count + 1)}> // 点击按钮时,将count加1Click me</button></div>);
}
useEffect:
import React, { useState, useEffect } from 'react'; // 从React库中导入useState和useEffect Hookfunction Example() { // 定义一个函数组件Exampleconst [count, setCount] = useState(0); // 声明count状态变量// 类似于 componentDidMount 和 componentDidUpdate:useEffect(() => { // useEffect Hook,用于在组件渲染后执行副作用// 更新文档标题document.title = `You clicked ${count} times`; // 设置文档标题为"你点击了count次"}, [count]); // 仅在count变化时执行return (<div><p>You clicked {count} times</p> // 显示点击次数<button onClick={() => setCount(count + 1)}> // 点击按钮时,将count加1Click me</button></div>);
}
七、高阶组件
高阶组件(HOC)是React中一个重要的概念,它本质上是一个函数,接收一个组件并返回一个新的增强版组件。
代理方式高阶组件:
function withLogger(WrappedComponent) { // 定义一个高阶组件withLogger,接收一个组件作为参数return class extends React.Component { // 返回一个新的组件componentDidMount() { // 组件挂载完成后执行console.log(`Component ${WrappedComponent.name} mounted`); // 在控制台打印组件名称}render() { // 返回渲染的组件return <WrappedComponent {...this.props} />; // 将传入的props传递给被包装的组件}};
}
使用方式:
const EnhancedComponent = withLogger(Welcome); // 使用withLogger高阶组件包装Welcome组件
八、实战:创建一个简单的组件
import React from 'react'; // 从React库中导入React对象
import ReactDOM from 'react-dom'; // 从react-dom库中导入ReactDOM对象class App extends React.Component { // 定义一个名为App的类组件,继承自React.Componentconstructor(props) { // 构造函数,初始化组件状态super(props); // 调用父类Component的构造函数this.state = { // 初始化组件状态message: 'Hello, React!' // message状态初始值为"Hello, React!"};}handleClick = () => { // 定义点击事件处理函数this.setState({ // 更新状态message: 'Button clicked!' // 将message状态更新为"Button clicked!"});}render() { // render方法,返回要渲染的UIreturn (<div> // 返回一个div容器<h1>{this.state.message}</h1> // 显示message状态的值<button onClick={this.handleClick}>Click Me</button> // 按钮的点击事件绑定到handleClick方法</div>);}
}ReactDOM.render(<App />, document.getElementById('root')); // 将App组件渲染到id为"root"的DOM元素中
九、学习资源推荐
1. 官方文档:React官网提供了最权威的文档和教程。
2. React视频教程:《React极速入门指南》、《Vue、Angular、React 项目开发与深度对比》。
3. 实践项目:通过构建实际项目(如电商网站、社交应用)来巩固知识。
4. 社区支持:React社区活跃,Stack Overflow、GitHub等平台有大量问题解答。
十、结语
React作为当今最流行的前端框架之一,以其组件化、声明式和高性能的特点,成为构建现代Web应用的首选。虽然学习曲线比Vue稍高,但掌握React将为你的前端开发技能带来巨大提升。
随着React生态系统的不断发展(如Redux、React Router、Hooks等),React的应用场景越来越广泛。无论你是前端新手还是有经验的开发者,学习React都将为你的职业发展带来重要价值。
开始你的React之旅,用组件化思维构建更高效、更可维护的Web应用吧!