学习 React【Plan - June - Week 1】
一、使用 JSX 书写标签语言
JSX 是一种 JavaScript 的语法扩展,React 使用它来描述用户界面。
什么是 JSX?
- JSX 是 JavaScript 的一种语法扩展。
- 看起来像 HTML,但它实际上是在 JavaScript 代码中写 XML/HTML。
- 浏览器并不能直接运行 JSX,需要通过打包工具(如 Babel)将其转译为 JavaScript。
示例:
const element = <h1>Hello, world!</h1>;
1、JSX 的基本规则
使用大写字母定义组件
function MyButton() {return <button>I'm a button</button>;
}
- 小写字母开头的标签,如
<div>
被解析为 HTML 标签。 - 大写字母开头的标签,如
<MyButton>
被解析为 React 组件。
必须使用闭合标签
- 所有标签必须闭合(类似 XML 语法)
// 正确
<input />
<br />
<MyComponent />// 错误
<input>
使用 {}
插入 JavaScript 表达式
const user = "小明";
const element = <h1>Hello, {user}!</h1>;
- 只能插入表达式(不是语句)
合法表达式:
{1 + 2}
{user.name}
{formatDate(date)}
非法语句:
{if (isTrue) { ... }}
{for (...) { ... }}
使用 className
代替 class
// HTML 写法
<div class="container"></div>// JSX 写法
<div className="container"></div>
因为 class
是 JavaScript 的关键字,所以要使用 className
。
使用 camelCase
的属性名
// HTML 写法
<input tabindex="0" onclick="handleClick()" />// JSX 写法
<input tabIndex={0} onClick={handleClick} />
2、条件渲染和列表渲染
条件渲染
使用三元表达式、逻辑与 &&
:
{isLoggedIn ? <LogoutButton /> : <LoginButton />}{messages.length > 0 && <Notification messages={messages} />}
列表渲染
使用 map()
进行循环输出,并为每个子元素设置唯一的 key
const items = ['A', 'B', 'C'];<ul>{items.map(item => <li key={item}>{item}</li>)}
</ul>
3、JSX 转换成 JavaScript 的原理
JSX 会被转译为 React.createElement
调用:
const element = <h1 className="title">Hello</h1>;// 会被转换为:
const element = React.createElement('h1', { className: 'title' }, 'Hello');
4、组合 JSX
JSX 支持嵌套结构:
function App() {return (<div><Header /><Content /><Footer /></div>);
}
可使用片段(Fragment)避免多余的 DOM 元素:
<><td>内容1</td><td>内容2</td>
</>
5、JSX Tips
注释写法:
{/* 这是注释 */}
多行 JSX 需要用括号包裹:
return (<div><h1>Hello</h1></div>
);
二、组件(Component)
React 应用是由组件构成的,组件是可以复用的 UI 单元。
什么是组件?
- 组件(Component) 是 React 的核心概念。
- 本质上是一个返回 JSX 的函数。
- 组件名称必须以大写字母开头。
示例:
function MyButton() {return <button>I'm a button</button>;
}
在 JSX 中使用:
export default function MyApp() {return (<div><h1>Welcome to my app</h1><MyButton /></div>);
}
组件命名规则
- 必须以大写字母开头,否则会被当成 HTML 标签。
- 使用 PascalCase 命名约定(每个单词首字母大写)。
1、组件是函数,不是标签
function MyButton() {return <button>Click me</button>;
}
这个 MyButton
是一个函数,而 <MyButton />
是它的使用方式(调用)。
2、组件可以复用
你可以多次使用同一个组件,它们是互相独立的:
function MyApp() {return (<div><MyButton /><MyButton /></div>);
}
每个 <MyButton />
都会渲染一个独立的按钮。
3、组件的结构建议
建议为每个组件建一个文件(例如 MyButton.jsx
),用于项目组织:
src/
├─ components/
│ └─ MyButton.jsx
└─ App.jsx
🧪 示例代码汇总
// MyButton.jsx
export default function MyButton() {return <button>I'm a button</button>;
}// App.jsx
import MyButton from './MyButton';export default function MyApp() {return (<div><h1>Welcome to my app</h1><MyButton /><MyButton /></div>);
}
三、State
React 的状态(state)允许组件“记住”信息。状态是让组件有“记忆”的机制,通常用于跟踪用户交互或界面变化。
1、什么是状态(State)?
- 状态是组件的“记忆”。
- 在每次重新渲染时,组件的状态保持不变。
- 状态的变化会 触发组件的重新渲染。
2、如何添加状态?
通过 useState
Hook:
import { useState } from 'react';function MyComponent() {const [count, setCount] = useState(0);
}
useState
解释:
const [state, setState] = useState(initialValue);
名称 | 含义 |
---|---|
state | 当前状态值 |
setState | 用于更新状态的函数 |
initialValue | 初始状态值 |
3、状态的基本用法示例
import { useState } from 'react';export default function MyButton() {const [count, setCount] = useState(0);function handleClick() {setCount(count + 1);}return (<button onClick={handleClick}>Clicked {count} times</button>);
}
4、每次点击发生了什么?
- 点击按钮时,
handleClick
被调用。 setCount(count + 1)
更新状态。- React 重新渲染组件。
- 新的
count
显示在界面上。
状态更新不会改变当前值,而是触发一次新的渲染,组件中的
count
会更新为新值。
5、状态在组件之间是隔离的
每个组件实例有自己独立的状态。
<MyButton />
<MyButton />
上面两个按钮互不影响,即使它们使用相同的 useState
。
6、不要直接修改 state 变量
// 错误写法(不会触发重新渲染)
count = count + 1;// 正确写法
setCount(count + 1);
只有通过 setCount
这样的更新函数,React 才会触发重新渲染。
7、多个状态变量
可以在一个组件中使用多个 useState
:
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
8、示例完整代码
import { useState } from 'react';function MyButton() {const [count, setCount] = useState(0);function handleClick() {setCount(count + 1);}return (<button onClick={handleClick}>Clicked {count} times</button>);
}export default function MyApp() {return (<div><h1>Welcome to my app</h1><MyButton /><MyButton /></div>);
}
四、响应事件
React 使用类似 HTML 的方式来处理用户交互事件,比如点击、输入、悬停等。但语法略有不同,并支持更强的逻辑功能。
1、事件绑定基础
React 使用 onClick
、onChange
等属性来绑定事件处理函数。
示例:
function MyButton() {function handleClick() {alert('你点击了我!');}return (<button onClick={handleClick}>点击我</button>);
}
注意:
- 使用驼峰命名(如
onClick
,而不是onclick
) - 事件处理函数是一个普通的 JavaScript 函数
- JSX 中不使用字符串绑定函数(不同于 HTML 的
onclick="handleClick()"
)
2、为什么使用函数名而不是函数调用?
// 正确
onClick={handleClick}// 错误(会立即执行)
onClick={handleClick()}
你应传递函数的引用,而不是函数的执行结果。
3、使用箭头函数传参
有时你希望传递参数给事件处理函数,可以使用箭头函数:
function handleClick(name) {alert(`Hello, ${name}!`);
}<button onClick={() => handleClick('小明')}>Say Hello
</button>
4、在组件中组合事件处理逻辑
React 鼓励你将组件拆成小块,事件处理函数可以在组件内部定义或向下传递:
function Button({ onClick, children }) {return <button onClick={onClick}>{children}</button>;
}function App() {function handleClick() {alert('Clicked!');}return (<div><Button onClick={handleClick}>按钮1</Button><Button onClick={handleClick}>按钮2</Button></div>);
}
5、常见事件类型
React 事件名 | 对应 HTML | 说明 |
---|---|---|
onClick | onclick | 点击事件 |
onChange | onchange | 输入/选择改变 |
onSubmit | onsubmit | 表单提交 |
onMouseEnter | onmouseenter | 鼠标进入 |
onKeyDown | onkeydown | 按键按下 |
6、阻止默认行为
可以在事件中调用 event.preventDefault()
:
function handleSubmit(e) {e.preventDefault();alert('提交已阻止');
}<form onSubmit={handleSubmit}><button type="submit">提交</button>
</form>
7、React 与原生 DOM 事件的区别
项目 | React | 原生 HTML |
---|---|---|
命名方式 | 驼峰命名,如 onClick | 小写,如 onclick |
传递方式 | 传函数引用 | 传字符串或函数调用 |
自动阻止冒泡 | 否,你仍需手动阻止冒泡 | 同样需手动处理 |
8、示例完整代码
function Button({ message, children }) {function handleClick() {alert(message);}return (<button onClick={handleClick}>{children}</button>);
}export default function App() {return (<div><Button message="你好!">点我</Button><Button message="再见!">再点我</Button></div>);
}
五、React 哲学
“React 哲学” 教你如何从 UI 设计图开始,一步步将页面拆解为组件,再构建出数据驱动的交互式界面。
示例场景简介
我们要实现一个可搜索的商品表格(Searchable Product Table),它包含:
- 一个搜索框
- 一个是否只显示有库存商品的勾选框
- 一个根据品类分组的商品表格
构建步骤总览
React 官方建议采用 五步法:
- 将 UI 拆解为组件层级结构
- 构建组件的静态版本(无交互)
- 确定最小但完整的 UI 状态表示
- 确定哪些组件拥有状态(状态提升)
- 添加反向数据流(处理用户输入)
1、第一步:将 UI 拆解为组件层级
观察 UI,并根据界面结构拆出以下组件:
组件层级结构
FilterableProductTable (父组件)
├─ SearchBar
└─ ProductTable├─ ProductCategoryRow└─ ProductRow
每个组件的职责
组件名 | 作用描述 |
---|---|
FilterableProductTable | 管理所有状态,整合其他组件 |
SearchBar | 输入搜索文本与是否过滤库存 |
ProductTable | 接收数据与过滤条件,渲染表格 |
ProductCategoryRow | 显示每个品类的标题 |
ProductRow | 显示单个商品 |
2、第二步:构建静态版本(无交互)
- 使用父组件
FilterableProductTable
将假数据通过 props 传给子组件。 - 每个组件只关注如何显示数据,不包含状态或交互。
- 假数据示例:
const PRODUCTS = [{category: "Fruits", price: "$1", stocked: true, name: "Apple"},{category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"},{category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"},{category: "Vegetables", price: "$2", stocked: true, name: "Spinach"},{category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"},{category: "Vegetables", price: "$1", stocked: true, name: "Peas"}
];
3、第三步:确定最小但完整的 UI 状态(State)
根据界面交互功能,确定需要驱动 UI 的状态:
- 搜索文本(
filterText
) - 是否只显示有库存商品(
inStockOnly
)
不是状态的内容(可由 props 或其他状态推导得出):
- 商品数据(是静态的)
- 分类标题(可从数据中提取)
- 筛选后的商品列表(由
filterText
+inStockOnly
计算)
4、第四步:决定状态的归属
状态应该放在最“靠上的共同祖先组件”中。
状态名 | 所属组件 | 原因 |
---|---|---|
filterText | FilterableProductTable | SearchBar 和 ProductTable 都使用它 |
inStockOnly | FilterableProductTable | 同上 |
5、第五步:添加反向数据流(提升状态 + 子传父)
让 SearchBar
接收 filterText
和 inStockOnly
作为 props,并通过 onChange
回调将用户输入传递给父组件修改状态。
function SearchBar({ filterText, inStockOnly, onFilterTextChange, onInStockChange }) {return (<form><inputtype="text"value={filterText}onChange={(e) => onFilterTextChange(e.target.value)}placeholder="Search..."/><label><inputtype="checkbox"checked={inStockOnly}onChange={(e) => onInStockChange(e.target.checked)}/>Only show products in stock</label></form>);
}
6、组件结构(最终)
<FilterableProductTable products={PRODUCTS} />// 内部包含└── <SearchBarfilterText={...}inStockOnly={...}onFilterTextChange={...}onInStockChange={...}/>└── <ProductTableproducts={...}filterText={...}inStockOnly={...}/>