🔴 jsx
⭕ JSX语法特点总结
1. 标签不需要引号
在JSX中写标签时不需要加引号,直接使用即可。
jsx
// 正确
const element = <div>Hello</div>;
// 错误
const element = "<div>Hello</div>";
2. 花括号内可嵌入表达式
花括号{}
内可以嵌入变量和表达式,但必须是能产生值的表达式。
jsx
let age = 20;
// 嵌入变量
<p>{age}</p>
// 嵌入表达式
<p>{age + 1}</p> // 显示21
// 三元表达式
<p>{age > 18 ? "成年人" : "青少年"}</p>
3. 表达式与语句的区别
- 表达式 :产生值,如
1+1
、age > 18 ? "成年人" : "青少年"
- 语句 :不产生值,如
if...else
、for
循环、console.log()
JSX中只能嵌入表达式,不能嵌入语句。
4. 标签必须闭合
所有标签都必须正确闭合,包括自闭合标签。
jsx
// 正确
<input />
<br />
// 错误
<input>
<br>
5. 最外层只能有一个元素
JSX表达式最外层必须有且只有一个根元素。
jsx
// 正确
<div>
<p>第一段</p>
<p>第二段</p>
</div>
// 错误
<p>第一段</p>
<p>第二段</p>
6. 注释写法
JSX中的注释需要写在花括号内。
jsx
{/* 这是一个注释 */}
<div>
{/* 显示当前时间戳 */}
{new Date().getTime()}
</div>
7. 样式处理
类名使用className
jsx
// JSX
<p className="active">红色文字</p>
// 对应HTML
<p class="active">红色文字</p>
内联样式使用对象
jsx
const style = {
color: 'blue',
backgroundColor: 'gray'
};
<p style={style}>带样式的文字</p>
等价于原生HTML:
html
<p style="color: blue; background-color: gray;">带样式的文字</p>
JSX语法提供了将JavaScript和HTML混合编写的能力,但需要注意:
- 只能使用表达式,不能使用语句
- 标签必须正确闭合
- 样式处理方式与原生HTML有所不同
- 注释需要特殊写法
🔴 遍历数据
⭕ 条件渲染 - 短路运算
1. 短路运算原理
在JavaScript中,逻辑运算符&&
和||
具有短路特性:
javascript
// 与运算(&&)
true && expression // 会执行expression
false && expression // 不会执行expression,直接返回false
// 或运算(||)
true || expression // 不会执行expression,直接返回true
false || expression // 会执行expression
2. React中的条件渲染应用
jsx
// 使用短路运算实现条件渲染
function App() {
const isLoggedIn = true; // 从状态或props获取
return (
<div>
{isLoggedIn && <AdminPanel />}
{!isLoggedIn && <LoginForm />}
</div>
);
}
// 等价于if-else写法
function App() {
const isLoggedIn = true;
let content;
if(isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return <div>{content}</div>;
}
⭕ 列表渲染 - map方法
1. 基础数组渲染
jsx
const bookList = [
{ id: 1, title: 'Python', price: 38.5 },
{ id: 2, title: 'JavaScript', price: 42.0 },
{ id: 3, title: 'React', price: 45.0 },
{ id: 4, title: 'Vue', price: 40.0 }
];
function BookList() {
return (
<ul>
{bookList.map((item) => (
<li key={item.id}>
{item.title} - 价格: {item.price}
</li>
))}
</ul>
);
}
2. 箭头函数写法注意事项
jsx
// 正确写法1 - 直接返回值(隐式return)
bookList.map(item => <li>{item.title}</li>)
// 正确写法2 - 使用大括号但显式return
bookList.map(item => {
return <li>{item.title}</li>;
})
// 错误写法 - 使用大括号但忘记return
bookList.map(item => {
<li>{item.title}</li> // 不会渲染,因为没有return
})
3. 复杂列表渲染示例
jsx
function BookList() {
return (
<div className="book-container">
<h2>书籍列表</h2>
<ul className="book-list">
{bookList.map(book => (
<li key={book.id} className="book-item">
<h3>{book.title}</h3>
<p>价格: ¥{book.price.toFixed(2)}</p>
<button onClick={() => addToCart(book)}>加入购物车</button>
</li>
))}
</ul>
</div>
);
}
⭕ 关键注意事项
-
key属性:列表中的每个元素都应该有一个唯一的key prop,通常是数据的ID
jsx// 正确 {items.map(item => <li key={item.id}>{item.name}</li>)} // 避免使用索引作为key(除非列表不会变化) {items.map((item, index) => <li key={index}>{item.name}</li>)}
-
复杂条件渲染:对于更复杂的条件,可以使用三元表达式或提取为函数
jsx// 三元表达式 {isLoading ? <Spinner /> : <Content />} // 提取为函数 function renderContent() { if(isLoading) return <Spinner />; if(isError) return <Error />; return <Content />; }
-
性能优化:避免在渲染函数中直接定义函数或创建新数组
jsx// 不推荐(每次渲染都创建新函数) {items.map(item => <Item onClick={() => handleClick(item)} />)} // 推荐(提前定义处理函数) function renderItem(item) { return <Item onClick={handleClick} data={item} />; }
🔴 虚拟dom
⭕ 虚拟DOM原理
1. 原生JavaScript渲染流程
javascript
// 原生JS直接操作真实DOM
const bookList = getDataFromAPI(); // 从API获取数据
const ul = document.createElement('ul');
bookList.forEach(book => {
const li = document.createElement('li');
li.textContent = `${book.title} - ¥${book.price}`;
ul.appendChild(li);
});
document.body.appendChild(ul);
// 数据更新时重新创建所有DOM
function updateBooks(newBooks) {
ul.innerHTML = ''; // 清空现有DOM
newBooks.forEach(book => {
const li = document.createElement('li');
li.textContent = `${book.title} - ¥${book.price}`;
ul.appendChild(li);
});
}
2. React虚拟DOM流程
jsx
// React使用虚拟DOM
function BookList({ books }) {
return (
<ul>
{books.map(book => (
<li key={book.id}>{book.title} - ¥{book.price}</li>
))}
</ul>
);
}
// 数据更新时React的对比过程
const oldVDOM = (
<ul>
<li key="10">Python - ¥38.5</li>
<li key="20">JavaScript - ¥42.0</li>
</ul>
);
const newVDOM = (
<ul>
<li key="15">C++ - ¥100</li>
<li key="10">Python - ¥38.5</li>
<li key="20">JavaScript - ¥42.0</li>
</ul>
);
// React会识别出只有key="15"是新元素
⭕ key属性的正确使用
1. 错误用法 - 使用数组索引
jsx
// 不推荐:使用数组索引作为key
{bookList.map((book, index) => (
<li key={index}>{book.title}</li>
))}
2. 正确用法 - 使用唯一ID
jsx
// 推荐:使用数据本身的唯一ID
{bookList.map(book => (
<li key={book.id}>{book.title}</li>
))}
3. 没有ID时的替代方案
jsx
// 如果没有ID,可以生成稳定hash
{bookList.map(book => {
const hash = `${book.title}-${book.price}`;
return <li key={hash}>{book.title}</li>;
})}
⭕ Diff算法详解
1. 对比过程示例
javascript
// 旧虚拟DOM
const oldTree = {
type: 'ul',
children: [
{ type: 'li', key: '10', content: 'Python' },
{ type: 'li', key: '20', content: 'JavaScript' }
]
};
// 新虚拟DOM
const newTree = {
type: 'ul',
children: [
{ type: 'li', key: '15', content: 'C++' },
{ type: 'li', key: '10', content: 'Python' },
{ type: 'li', key: '20', content: 'JavaScript' }
]
};
// Diff算法会:
// 1. 比较ul类型相同
// 2. 比较子元素:
// - 发现key="15"是新元素 → 创建
// - 发现key="10"存在 → 复用
// - 发现key="20"存在 → 复用
2. 使用索引作为key的问题
javascript
// 如果使用索引作为key,添加元素时:
const oldTreeWithIndex = {
children: [
{ key: 0, content: 'Python' }, // 现在key=0
{ key: 1, content: 'JavaScript' } // key=1
]
};
const newTreeWithIndex = {
children: [
{ key: 0, content: 'C++' }, // 现在key=0
{ key: 1, content: 'Python' }, // key=1 (原来是Python)
{ key: 2, content: 'JavaScript' } // key=2
]
};
// Diff结果:
// key=0: 内容从Python变为C++ → 更新
// key=1: 内容从JavaScript变为Python → 更新
// key=2: 新增 → 创建
// 实际上只需要添加C++,但导致所有元素都被处理
⭕ 性能优化建议
- 始终使用稳定key:避免使用索引,使用数据唯一标识
- 避免频繁重新渲染:使用React.memo、useMemo等优化
- 最小化DOM操作:虚拟DOM的优势在于批量更新
- 复杂列表考虑虚拟滚动:对于超长列表使用react-window等库
jsx
// 优化示例
const MemoBookItem = React.memo(({ book }) => {
return <li>{book.title} - ¥{book.price}</li>;
});
function OptimizedBookList({ books }) {
return (
<ul>
{books.map(book => (
<MemoBookItem key={book.id} book={book} />
))}
</ul>
);
}
🔴 组件
⭕ 组件基础概念
组件是React应用的核心构建块,它将UI拆分为独立可复用的代码片段。组件主要包含三要素:
- HTML(JSX) - 负责UI结构
- CSS - 负责样式表现
- JavaScript - 负责逻辑交互
⭕ 函数组件开发
1. 基本结构
jsx
// 函数组件基本形式
function HelloFC() {
// 数据定义
const message = "中国联通";
// 返回JSX
return (
<div>
<p>{message}</p>
</div>
);
}
// 使用组件
ReactDOM.render(<HelloFC />, document.getElementById('root'));
2. 函数组件特点
- 使用
function
关键字定义 - 必须返回JSX
- 数据定义在函数体内
- 简单直接,适合无状态UI
⭕ 类组件开发
1. 基本结构
jsx
// 类组件基本形式
class HelloCC extends React.Component {
// 数据定义
bookList = [
{ id: 1, title: 'Python', price: 38.5 },
{ id: 2, title: 'JavaScript', price: 42.0 }
];
// 必须有的render方法
render() {
return (
<ul>
{this.bookList.map(book => (
<li key={book.id}>
{book.title} - ¥{book.price}
</li>
))}
</ul>
);
}
}
// 使用组件
ReactDOM.render(<HelloCC />, document.getElementById('root'));
2. 类组件特点
- 使用
class
关键字定义 - 必须继承
React.Component
- 必须有
render()
方法 - 数据定义在类中,通过
this
访问 - 功能更强大,适合复杂组件
⭕ this关键字详解
1. this在类中的使用
javascript
class Person {
data = {
name: '院',
age: 22
};
showInfo() {
// 正确访问实例属性
console.log(this.data);
// 错误写法(直接访问data会报错)
// console.log(data); // ReferenceError: data is not defined
}
}
const p1 = new Person();
p1.showInfo(); // 正确输出 {name: "院", age: 22}
2. this指向规则
- 在类方法中,
this
指向当前类的实例对象 - 谁调用方法,方法中的
this
就指向谁 - 在React类组件中,
render()
方法中的this
指向组件实例
⭕ 组件对比与选择
特性 | 函数组件 | 类组件 |
---|---|---|
语法 | 函数声明 | class继承 |
状态 | 需使用Hooks | 内置state |
生命周期 | 使用Hooks模拟 | 内置方法 |
this | 无this | 需要使用this |
代码量 | 简洁 | 相对复杂 |
适用场景 | 简单UI、无状态 | 复杂逻辑、有状态 |
⭕ 最佳实践建议
- 新项目优先使用函数组件:配合Hooks可以实现绝大多数功能
- 理解this机制:类组件开发必须掌握this指向
- 组件拆分原则:单一职责,一个组件只做一件事
- 命名规范:组件名使用PascalCase(大驼峰)命名法
- props使用:无论是函数还是类组件,都通过props接收外部数据
jsx
// 函数组件接收props
function Greeting(props) {
return <h1>Hello, {props.name}</h1>;
}
// 类组件接收props
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
🔴 组件嵌套
⭕ 组件嵌套基础概念
组件嵌套是React开发中的核心模式,它允许我们将UI拆分为独立可复用的部分,并通过组合方式构建复杂界面。
基本嵌套示例
jsx
// 函数组件
function HelloFC() {
return (
<div>
<p>中国联通</p>
<HelloCC /> {/* 嵌套类组件 */}
</div>
);
}
// 类组件
class HelloCC extends React.Component {
render() {
return (
<ul>
{this.props.books.map(book => (
<li key={book.id}>{book.title}</li>
))}
</ul>
);
}
}
// 使用嵌套组件
ReactDOM.render(<HelloFC />, document.getElementById('root'));
⭕ 容器组件模式
1. 容器组件结构
jsx
// 容器组件(通常为类组件)
class App extends React.Component {
render() {
return (
<div className="app-container">
<Header />
<MainContent />
<Footer />
</div>
);
}
}
// 使用容器组件
ReactDOM.render(<App />, document.getElementById('root'));
2. 实际项目中的典型结构
jsx
// App.js (根组件)
function App() {
return (
<div className="app">
<Navbar />
<div className="content">
<Sidebar />
<Main />
</div>
<Footer />
</div>
);
}
// Main.js (内容组件)
function Main() {
return (
<main>
<Banner />
<ProductList />
<Newsletter />
</main>
);
}
⭕ 组件嵌套关键点
-
组件引用语法:
jsx// 正确引用方式 <ComponentName /> // 错误方式(会报错) ComponentName
-
组件通信方式:
jsx// 父传子 - 通过props function Parent() { return <Child message="Hello" />; } function Child(props) { return <p>{props.message}</p>; }
-
组件位置规则:
- 组件必须首字母大写(PascalCase)
- 组件可以在任何其他组件中使用
- 组件可以多层嵌套
⭕ 复杂嵌套示例
jsx
// 用户信息卡片组件
function UserCard({ user }) {
return (
<div className="user-card">
<Avatar image={user.avatar} />
<div className="user-info">
<UserName name={user.name} />
<UserStats stats={user.stats} />
</div>
</div>
);
}
// 用户列表组件
function UserList({ users }) {
return (
<div className="user-list">
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
// 主页面组件
function HomePage() {
const [users, setUsers] = useState([]);
useEffect(() => {
// 获取用户数据
fetchUsers().then(data => setUsers(data));
}, []);
return (
<div className="home-page">
<Header />
<Sidebar />
<MainContent>
<UserList users={users} />
<RecentActivities />
</MainContent>
<Footer />
</div>
);
}
⭕ 最佳实践建议
- 单一职责原则:每个组件只负责一个特定功能
- 合理拆分:当组件超过200行代码时考虑拆分
- props命名:使用清晰明确的props名称
- 避免过度嵌套:通常不超过3-4层嵌套
- 容器与展示分离 :
- 容器组件:负责数据获取和状态管理
- 展示组件:负责UI渲染
jsx
// 容器组件示例
function UserContainer() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers().then(setUsers);
}, []);
return <UserList users={users} />;
}
// 展示组件示例
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<UserItem key={user.id} user={user} />
))}
</ul>
);
}
🔴 绑定事件
⭕ 事件绑定的核心问题
1. 原生DOM与React事件绑定区别
jsx
// 原生HTML事件绑定
<button onclick="handleClick()">Click</button>
// React事件绑定
<button onClick={handleClick}>Click</button>
关键区别:
- React使用小驼峰命名(onClick)
- 不需要引号包裹
- 传递函数引用而非字符串
⭕ 类组件中事件绑定的三种解决方案
方案1:箭头函数包裹
jsx
class App extends React.Component {
data = [1, 2, 3];
handleClick = () => {
console.log(this.data); // 正确访问实例属性
};
render() {
return (
<button onClick={() => this.handleClick()}>
按钮1
</button>
);
}
}
方案2:bind绑定
jsx
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.data); // 正确访问实例属性
}
render() {
return (
<button onClick={this.handleClick}>
按钮2
</button>
);
}
}
方案3:类属性箭头函数
jsx
class App extends React.Component {
handleClick = () => {
console.log(this.data); // 正确访问实例属性
};
render() {
return (
<button onClick={this.handleClick}>
按钮3
</button>
);
}
}
⭕ this丢失原理分析
javascript
class Example {
value = 42;
method() {
console.log(this.value);
}
}
const instance = new Example();
const method = instance.method; // this丢失
method(); // TypeError: Cannot read property 'value' of undefined
React事件绑定中的this丢失问题本质上是由于函数引用传递导致的上下文丢失。
⭕ 事件对象(event)的使用
jsx
class App extends React.Component {
handleClick = (e) => {
console.log('事件对象:', e);
console.log('目标元素:', e.target);
console.log('按钮文本:', e.target.innerHTML);
console.log('按钮值:', e.target.value);
};
render() {
return (
<button onClick={this.handleClick}>
点击我
</button>
);
}
}
⭕ 性能优化建议
-
避免在render中使用箭头函数:
jsx// 不推荐(每次渲染创建新函数) <button onClick={() => this.handleClick()}> // 推荐(提前绑定) <button onClick={this.handleClick}>
-
参数传递方式:
jsx// 方式1:bind传参 <button onClick={this.handleClick.bind(this, id)}> // 方式2:箭头函数传参 <button onClick={(e) => this.handleClick(id, e)}> // 方式3:data-attributes <button data-id={id} onClick={this.handleClick}>
-
事件委托:对于列表项事件,推荐在父元素上处理
⭕ 综合示例
jsx
class EventDemo extends React.Component {
state = {
count: 0,
items: ['Item 1', 'Item 2', 'Item 3']
};
// 方案3:类属性箭头函数
handleIncrement = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
// 方案2:constructor中bind
constructor(props) {
super(props);
this.handleDecrement = this.handleDecrement.bind(this);
}
handleDecrement() {
this.setState(prev => ({ count: prev.count - 1 }));
}
// 带参数的事件处理
handleItemClick = (index, e) => {
console.log(`点击了第${index + 1}项`, e.target);
};
render() {
return (
<div>
<h1>计数器: {this.state.count}</h1>
{/* 三种绑定方式示例 */}
<button onClick={this.handleIncrement}>增加</button>
<button onClick={this.handleDecrement}>减少</button>
<button onClick={() => console.log('内联箭头函数')}>
内联箭头函数
</button>
{/* 列表项事件处理 */}
<ul>
{this.state.items.map((item, index) => (
<li
key={index}
onClick={(e) => this.handleItemClick(index, e)}
>
{item}
</li>
))}
</ul>
</div>
);
}
}
🔴 refs属性
⭕ Refs 的核心作用
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。主要用途包括:
- 管理焦点、文本选择或媒体播放
- 触发强制动画
- 集成第三方 DOM 库
⭕ 创建和使用 Refs 的步骤
1. 创建 Refs 对象(推荐方式)
jsx
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 第一步:创建 Refs
this.myRef = React.createRef();
}
render() {
// 第二步:绑定 Refs
return <div ref={this.myRef} />;
}
}
2. 访问 Refs
jsx
componentDidMount() {
// 第三步:访问 DOM 节点
const node = this.myRef.current;
console.log(node);
}
⭕ 不同场景下的 Refs 使用
1. 访问 DOM 元素
jsx
class TextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
focusTextInput = () => {
// 直接访问 DOM API
this.textInput.current.focus();
};
render() {
return (
<div>
<input type="text" ref={this.textInput} />
<button onClick={this.focusTextInput}>
聚焦输入框
</button>
</div>
);
}
}
2. 访问类组件实例
jsx
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
componentDidMount() {
// 调用子组件的方法
this.textInput.current.focusTextInput();
}
render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}
3. 函数组件中使用 Refs(forwardRef)
jsx
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="fancy-button">
{props.children}
</button>
));
// 父组件中使用
const ref = React.createRef();
<FancyButton ref={ref}>点击我</FancyButton>;
⭕ 回调 Refs(旧版 API)
jsx
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
}
focusTextInput = () => {
if (this.textInput) this.textInput.focus();
};
render() {
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<button onClick={this.focusTextInput}>
聚焦输入框
</button>
</div>
);
}
}
⭕ Refs 使用注意事项
- 避免过度使用 Refs:优先考虑使用 React 的数据流(props 和 state)
- 不要在函数组件上使用 ref 属性:除非使用 forwardRef
- componentDidMount 或 componentDidUpdate 中访问 Refs:确保 DOM 已经渲染完成
- 避免在 render 方法中访问 refs:此时 ref 可能还未被设置
⭕ 综合示例
jsx
class RefDemo extends React.Component {
constructor(props) {
super(props);
// 创建多个 Refs
this.inputRef = React.createRef();
this.divRef = React.createRef();
this.childRef = React.createRef();
}
componentDidMount() {
// 自动聚焦输入框
this.inputRef.current.focus();
// 获取 DOM 元素信息
console.log('Div 高度:', this.divRef.current.clientHeight);
}
handleClick = () => {
// 调用子组件方法
this.childRef.current.sayHello();
// 获取输入值
alert(`输入的值: ${this.inputRef.current.value}`);
};
render() {
return (
<div ref={this.divRef}>
<h2>Refs 示例</h2>
<input
type="text"
ref={this.inputRef}
placeholder="自动聚焦的输入框"
/>
<ChildComponent ref={this.childRef} />
<button onClick={this.handleClick}>
执行操作
</button>
</div>
);
}
}
class ChildComponent extends React.Component {
sayHello = () => {
alert('Hello from Child Component!');
};
render() {
return <div>子组件</div>;
}
}
🔴 state属性
⭕ State 基础概念
State 是 React 组件内部用于存储和管理数据的对象,它是 React 数据驱动视图的核心机制。
1. State 的特点
- 组件私有:只能在组件内部访问和修改
- 动态变化:state 变化会触发组件重新渲染
- 不可直接修改 :必须通过
setState()
方法更新
⭕ State 基本使用
1. 初始化 State
jsx
class PasswordToggle extends React.Component {
constructor(props) {
super(props);
// 初始化 state
this.state = {
inputType: 'password',
buttonText: '显示'
};
}
render() {
return (
<div>
<input type={this.state.inputType} />
<button onClick={this.togglePassword}>
{this.state.buttonText}
</button>
</div>
);
}
}
2. 更新 State 的正确方式
jsx
togglePassword = () => {
// 错误方式:直接修改 state 不会触发重新渲染
// this.state.inputType = 'text';
// 正确方式:使用 setState
this.setState({
inputType: this.state.inputType === 'password' ? 'text' : 'password',
buttonText: this.state.inputType === 'password' ? '隐藏' : '显示'
});
};
⭕ setState 深入理解
1. setState 是异步的
jsx
updateCount = () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 可能不是最新值
// 获取更新后的 state
this.setState({ count: this.state.count + 1 }, () => {
console.log('更新后的值:', this.state.count);
});
};
2. 基于前一个 state 更新
jsx
incrementCount = () => {
// 推荐方式:使用函数形式确保基于最新 state
this.setState(prevState => ({
count: prevState.count + 1
}));
};
⭕ State 使用最佳实践
1. 避免深层嵌套
jsx
// 不推荐
this.state = {
user: {
profile: {
name: '',
age: 0
}
}
};
// 推荐:扁平化 state
this.state = {
userName: '',
userAge: 0
};
2. 不可变数据
jsx
// 数组更新
this.setState(prevState => ({
items: [...prevState.items, newItem]
}));
// 对象更新
this.setState(prevState => ({
user: {
...prevState.user,
name: newName
}
}));
⭕ 密码切换完整示例
jsx
class PasswordToggle extends React.Component {
constructor(props) {
super(props);
this.state = {
inputType: 'password',
buttonText: '显示密码'
};
}
togglePassword = () => {
this.setState(prevState => ({
inputType: prevState.inputType === 'password' ? 'text' : 'password',
buttonText: prevState.inputType === 'password' ? '隐藏密码' : '显示密码'
}));
};
render() {
return (
<div className="password-container">
<input
type={this.state.inputType}
placeholder="输入密码"
/>
<button onClick={this.togglePassword}>
{this.state.buttonText}
</button>
</div>
);
}
}
⭕ 常见问题解答
1. 为什么必须使用 setState?
- 直接修改 state 不会触发组件重新渲染
- setState 会合并 state 更新并批量处理
- setState 会触发 React 的协调过程(reconciliation)
2. 为什么 state 要命名为 state?
- React.Component 基类内置了 setState 方法
- setState 方法会专门处理 this.state 对象
- 约定优于配置,保持代码一致性
3. 如何组织复杂 state?
- 将相关 state 分组为多个 state 变量
- 对于复杂逻辑,考虑使用 useReducer (函数组件)
- 避免深层嵌套的数据结构
🔴 props属性基本操作
⭕ State 的核心概念
-
State 的作用:
- State 是 React 组件的核心状态属性
- 修改 state 值后会触发重新渲染
-
setState 的行为:
- 每次调用 setState 会做两件事:
- 修改 state 值
- 重新调用 render 函数进行渲染
- 每次调用 setState 会做两件事:
jsx
// 示例:setState 触发重新渲染
toggle = () => {
this.setState({ isActive: !this.state.isActive }); // 修改值并触发重新渲染
}
- 重要警告 :
- 绝对不能在 render 方法中调用 setState,会导致无限循环
jsx
// 错误示例:会导致无限循环
render() {
this.setState({ number: this.state.number + 1 }); // 错误!
return <div>{this.state.number}</div>;
}
⭕ Props 详解
父子组件概念
- 组件嵌套时,外层是父组件,内层是子组件
- 同级组件称为兄弟组件
Props 基本使用
- 父组件传递数据 :
- 通过属性方式传递数据给子组件
jsx
// 父组件传递数据
<ShowPerson
name="Eric"
age={32}
gender="男"
/>
- 子组件接收数据 :
- 通过 this.props 接收父组件传递的数据
jsx
// 子组件接收数据
class ShowPerson extends React.Component {
render() {
const { name, age, gender } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{gender}</li>
</ul>
);
}
}
Props 验证
-
为什么需要验证:
- 确保传递的数据类型正确
- 确保必填字段有值
- 限制枚举值的范围
-
使用 prop-types 库:
jsx
// 引入 prop-types
import PropTypes from 'prop-types';
class ShowPerson extends React.Component {
// 类静态属性方式定义 propTypes
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
gender: PropTypes.oneOf(['男', '女']).isRequired
};
render() {
// ...
}
}
- 常见验证类型 :
PropTypes.string
- 字符串PropTypes.number
- 数字PropTypes.bool
- 布尔值PropTypes.array
- 数组PropTypes.object
- 对象PropTypes.func
- 函数PropTypes.oneOf(['值1', '值2'])
- 枚举值
⭕ 类组件的静态属性和方法
- 静态成员 :
- 使用
static
关键字定义 - 属于类本身,而不是实例
- 使用
jsx
class Person {
// 静态属性
static message = "Hello React";
// 静态方法
static bar() {
console.log("静态方法");
}
// 实例方法
sayHello() {
console.log("实例方法");
}
}
// 使用静态成员
Person.message; // 访问静态属性
Person.bar(); // 调用静态方法
// 使用实例成员
const p = new Person();
p.sayHello(); // 调用实例方法
- 为什么 propTypes 要定义为静态属性 :
- propTypes 属于组件类的元信息
- 不需要每个实例都保存一份
- 可以保持代码组织更清晰
⭕ 数据传递的注意事项
- 数据类型问题 :
- 注意数字和字符串的区别
- 错误的类型可能导致意外行为
jsx
// 正确:传递数字
<ShowPerson age={32} />
// 错误:传递字符串数字
<ShowPerson age="32" /> // 会导致 "32" + 1 = "321"
- 解构传递 :
- 可以将对象解构为多个 props
jsx
const personInfo = {
name: "张三",
age: 32,
gender: "男"
};
<ShowPerson {...personInfo} />
- 默认值 :
- 可以为 props 设置默认值
jsx
static defaultProps = {
age: 18,
gender: '未知'
};
🔴 子组件传递数据给父组件
⭕ 核心概念
子组件向父组件传递数据的基本原理是:父组件通过props向子组件传递一个函数,子组件调用这个函数来间接修改父组件的状态。
⭕ 实现步骤
1. 父组件准备
jsx
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
// 父组件定义修改自身状态的方法
changeNumber = () => {
this.setState(prevState => ({
number: prevState.number + 1
}));
}
render() {
return (
<div>
<p>父组件的当前number值: {this.state.number}</p>
{/* 将方法和状态传递给子组件 */}
<Child
changeParentNumber={this.changeNumber}
currentNumber={this.state.number}
/>
</div>
);
}
}
2. 子组件实现
jsx
class Child extends React.Component {
handleClick = () => {
// 调用父组件传递过来的方法
this.props.changeParentNumber();
}
render() {
return (
<div>
<button onClick={this.handleClick}>
子组件按钮 - 点击增加父组件的number
</button>
<p>从父组件接收的number值: {this.props.currentNumber}</p>
</div>
);
}
}
⭕ 两种实现方式对比
方式一:直接传递方法
jsx
// 父组件
changeNumber = () => {
this.setState({ number: this.state.number + 1 });
}
// 传递给子组件
<Child changeParentNumber={this.changeNumber} />
// 子组件调用
this.props.changeParentNumber();
方式二:传递带参数的方法
jsx
// 父组件
changeNumber = (newValue) => {
this.setState({ number: newValue });
}
// 传递给子组件
<Child
changeParentNumber={this.changeNumber}
currentNumber={this.state.number}
/>
// 子组件调用
this.props.changeParentNumber(this.props.currentNumber + 1);
⭕ 关键点说明
-
this绑定问题:
- 推荐使用箭头函数定义方法,避免this指向问题
- 或者在构造函数中绑定this:
this.changeNumber = this.changeNumber.bind(this)
-
数据流向:
- 数据总是从父组件流向子组件(单向数据流)
- 子组件不能直接修改父组件的props
- 必须通过父组件提供的方法间接修改
-
最佳实践:
- 父组件方法命名清晰,如
handleXXX
或onXXX
- 子组件props命名有意义,如
onClick
、onChange
等 - 考虑使用函数组件和hooks简化代码
- 父组件方法命名清晰,如
⭕ 完整示例代码
jsx
import React from 'react';
// 父组件
class Parent extends React.Component {
state = {
number: 0
};
// 使用箭头函数避免this绑定问题
incrementNumber = () => {
this.setState(prevState => ({
number: prevState.number + 1
}));
};
render() {
return (
<div>
<h2>父组件</h2>
<p>当前值: {this.state.number}</p>
<Child
onIncrement={this.incrementNumber}
currentValue={this.state.number}
/>
</div>
);
}
}
// 子组件
class Child extends React.Component {
handleClick = () => {
this.props.onIncrement();
};
render() {
return (
<div>
<h3>子组件</h3>
<button onClick={this.handleClick}>
增加父组件的值
</button>
<p>从父组件接收的值: {this.props.currentValue}</p>
</div>
);
}
}
export default Parent;
⭕ 函数组件版本
jsx
import React, { useState } from 'react';
// 父组件
function Parent() {
const [number, setNumber] = useState(0);
const incrementNumber = () => {
setNumber(prev => prev + 1);
};
return (
<div>
<h2>父组件</h2>
<p>当前值: {number}</p>
<Child
onIncrement={incrementNumber}
currentValue={number}
/>
</div>
);
}
// 子组件
function Child({ onIncrement, currentValue }) {
return (
<div>
<h3>子组件</h3>
<button onClick={onIncrement}>
增加父组件的值
</button>
<p>从父组件接收的值: {currentValue}</p>
</div>
);
}
export default Parent;