React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。
- React 组件是返回标签的 JavaScript 函数:
注意:React 组件必须以大写字母开头,而 HTML 标签则必须是小写字母function MyButton() { return ( <button>I'm a button</button> ); }
使用 JSX 编写标签
-
上面所使用的标签语法被称为 JSX。是可选的,但大多数 React 项目会使用 JSX,主要是它很方便。所有推荐的本地开发工具 都开箱即用地支持 JSX。
-
JSX 比 HTML 更加严格。必须闭合标签,如 < br />。组件也不能返回多个 JSX 标签。必须将它们包裹到一个共享的父级中,比如 < div>…< /div> 或使用空的 <>…</> 包裹:
添加样式
-
在 React 中,可以使用 className 来指定一个 CSS 的 class。与 HTML 的 class 属性的工作方式相同:
<img className="avatar" />
-
然后,在一个单独的 CSS 文件中编写 CSS 规则:
/* In your CSS */ .avatar { border-radius: 50%; }
显示数据
-
JSX 会把标签放到 JavaScript 中。而大括号里的内容会 “回到” JavaScript 中,这样就可以从代码中嵌入一些变量并展示给用户。例如,将显示 user.name:
return ( <h1> {user.name} </h1> );
-
还可以将 JSX 属性 “转义到 JavaScript”,但必须使用
大括号
而非引号
。例如,className=“avatar” 是将 “avatar” 字符串传递给 className,作为 CSS 的 class。但 src={user.imageUrl} 会读取 JavaScript 的 user.imageUrl 变量,然后将该值作为 src 属性传递:return ( <img className="avatar" src={user.imageUrl} /> );
-
可以把更为复杂的表达式放入 JSX 的大括号内,例如 字符串拼接:
const user = { name: 'Hedy Lamarr', imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg', imageSize: 90, }; export default function Profile() { return ( <> <h1>{user.name}</h1> <img className="avatar" src={user.imageUrl} alt={'Photo of ' + user.name} style={{ width: user.imageSize, height: user.imageSize }} /> </> ); }
-
在上面示例中,style={{}} 并不是一个特殊的语法,而是 style={ } JSX 大括号内的一个普通 {} 对象。当样式依赖于 JavaScript 变量时,可以使用 style 属性。
条件渲染
-
React 没有特殊的语法来编写条件语句,因此使用的是普通的 JavaScript 代码。例如使用 if 语句根据条件引入 JSX:
let content; if (isLoggedIn) { content = <AdminPanel />; } else { content = <LoginForm />; } return ( <div> {content} </div> );
-
如果喜欢更为紧凑的代码,可以使用 条件 ? 运算符。与 if 不同的是,它工作于 JSX 内部:
<div> {isLoggedIn ? ( <AdminPanel /> ) : ( <LoginForm /> )} </div>
-
当不需要 else 分支时,还可以使用 逻辑 && 语法:
<div> {isLoggedIn && <AdminPanel />} </div>
-
所有这些方法也适用于有条件地指定属性。
渲染列表
-
依赖 JavaScript 的特性,例如 for 循环 和 array 的 map() 函数 来渲染组件列表。
-
假设有一个产品数组:
const products = [ { title: 'Cabbage', id: 1 }, { title: 'Garlic', id: 2 }, { title: 'Apple', id: 3 }, ];
-
在组件中,使用 map() 函数将这个数组转换为 < li> 标签构成的列表:
const listItems = products.map(product => <li key={product.id}> {product.title} </li> ); return ( <ul>{listItems}</ul> );
注意, < li> 有一个 key 属性。对于列表中的每一个元素,都应该传递一个字符串或者数字给 key,用于在其兄弟节点中唯一标识该元素。通常 key 来自后端返回的数据,比如数据库中的 ID。如果在后续插入、删除或重新排序这些项目,React 将依靠提供的 key 来思考发生了什么。
const products = [ { title: 'Cabbage', isFruit: false, id: 1 }, { title: 'Garlic', isFruit: false, id: 2 }, { title: 'Apple', isFruit: true, id: 3 }, ]; export default function ShoppingList() { const listItems = products.map(product => <li key={product.id} style={{ color: product.isFruit ? 'magenta' : 'darkgreen' }} > {product.title} </li> ); return ( <ul>{listItems}</ul> ); }
响应事件
-
可以通过在组件中声明
事件处理
函数来响应事件:function MyButton() { function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); }
注意,onClick={handleClick} 的结尾没有小括号!不要
调用
事件处理函数:只需 把函数传递给事件 即可。当用户点击按钮时 React 会调用传递的事件处理函数。
更新界面
-
通常希望组件 “记住” 一些信息并展示出来,比如一个按钮被点击的次数。要做到这一点,需要在组件中添加 state。
-
首先,从 React 引入 useState:
import { useState } from 'react';
-
在组件中声明一个 state 变量:
function MyButton() { const [count, setCount] = useState(0); // ...
-
从 useState 中获得两样东西:当前的 state(count),以及用于更新它的函数(setCount)。可以给它们起任何名字,但按照惯例会像 [something, setSomething] 这样命名。
-
第一次显示按钮时,count 的值为 0,因为把 0 传给了 useState()。当想改变 state 时,调用 setCount() 并将新的值传递给它。点击该按钮计数器将递增:
function MyButton() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> Clicked {count} times </button> ); }
-
React 将再次调用组件函数。第一次 count 变成 1。接着点击会变成 2。继续点击会逐步递增。
-
如果多次渲染同一个组件,每个组件都会拥有自己的 state。可以尝试点击不同的按钮:
import { useState } from 'react'; export default function MyApp() { return ( <div> <h1>Counters that update separately</h1> <MyButton /> <MyButton /> </div> ); } function MyButton() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> Clicked {count} times </button> ); }
-
注意,每个按钮会 “记住” 自己的 count,而不影响其他按钮。
使用 Hook
-
以 use 开头的函数被称为 Hook。useState 是 React 提供的一个内置 Hook。可以在 React API 中找到其他内置的 Hook。也可以通过组合现有的 Hook 来编写属于自己的 Hook。
-
Hook 比普通函数更为严格。只能在组件(或其他 Hook)的 顶层 调用 Hook。如果想在一个条件或循环中使用 useState,需要提取一个新的组件并在组件内部使用它。
组件间共享数据
1. 父组件向子组件传递数据(单向数据流)
这是最基础的组件间数据传递方式,通过 props 实现。父组件可以将自身的数据作为属性传递给子组件,子组件接收并使用这些数据。
- 示例代码
// 父组件 import React from 'react'; import ChildComponent from './ChildComponent'; const ParentComponent = () => { const sharedData = "Hello from parent!"; return ( <div> <ChildComponent data={sharedData} /> </div> ); }; export default ParentComponent; // 子组件 import React from 'react'; const ChildComponent = (props) => { return ( <div> <p>{props.data}</p> </div> ); }; export default ChildComponent;
2. 子组件向父组件传递数据
子组件向父组件传递数据通常通过回调函数实现。父组件将一个函数作为 prop 传递给子组件,子组件在需要的时候调用这个函数并传递数据。
- 示例代码
// 父组件 import React, { useState } from 'react'; import ChildComponent from './ChildComponent'; const ParentComponent = () => { const [receivedData, setReceivedData] = useState(''); const handleDataFromChild = (data) => { setReceivedData(data); }; return ( <div> <ChildComponent onSendData={handleDataFromChild} /> <p>Received from child: {receivedData}</p> </div> ); }; export default ParentComponent; // 子组件 import React from 'react'; const ChildComponent = (props) => { const sendDataToParent = () => { const data = "Hello from child!"; props.onSendData(data); }; return ( <div> <button onClick={sendDataToParent}>Send data to parent</button> </div> ); }; export default ChildComponent;
3. 跨级组件(祖先组件与后代组件)传递数据
如果组件层级较深,使用 props 层层传递数据会变得繁琐,此时可以使用 React 的 Context API。Context 提供了一种在组件之间共享数据的方式,而不必显式地通过组件树逐层传递 props。
- 示例代码
// 创建一个 Context import React, { createContext, useContext, useState } from 'react'; const MyContext = createContext(); // 祖先组件 const AncestorComponent = () => { const [sharedData, setSharedData] = useState('Shared data from ancestor'); return ( <MyContext.Provider value={{ sharedData, setSharedData }}> <DescendantComponent /> </MyContext.Provider> ); }; // 后代组件 const DescendantComponent = () => { const { sharedData } = useContext(MyContext); return ( <div> <p>{sharedData}</p> </div> ); }; export default AncestorComponent;
4. 非嵌套组件间共享数据
对于非嵌套关系的组件间共享数据,可以使用状态管理库,如 Redux、MobX 或 React 的 useReducer 结合 Context。这里以 Redux 为例简单说明:
-
安装 Redux 和 React-Redux
npm install redux react-redux
-
示例代码:
// actions.js export const updateData = (newData) => { return { type: 'UPDATE_DATA', payload: newData }; }; // reducer.js const initialState = { sharedData: '' }; const rootReducer = (state = initialState, action) => { switch (action.type) { case 'UPDATE_DATA': return { ...state, sharedData: action.payload }; default: return state; } }; export default rootReducer; // store.js import { createStore } from 'redux'; import rootReducer from './reducer'; const store = createStore(rootReducer); export default store; // 组件 A import React from 'react'; import { useDispatch } from 'react-redux'; import { updateData } from './actions'; const ComponentA = () => { const dispatch = useDispatch(); const handleUpdateData = () => { dispatch(updateData('New shared data')); }; return ( <div> <button onClick={handleUpdateData}>Update shared data</button> </div> ); }; // 组件 B import React from 'react'; import { useSelector } from 'react-redux'; const ComponentB = () => { const sharedData = useSelector((state) => state.sharedData); return ( <div> <p>{sharedData}</p> </div> ); }; // 根组件 import React from 'react'; import { Provider } from 'react-redux'; import store from './store'; import ComponentA from './ComponentA'; import ComponentB from './ComponentB'; const App = () => { return ( <Provider store={store}> <ComponentA /> <ComponentB /> </Provider> ); }; export default App;