This chapter’s Objectives:
- How to create and nest components
- How to add markup and styles
- How to display data
- How to render conditions and lists
- How to respond to events and update the screen
- How to share data between components
render 渲染; respond 响应;markup 标签
Creating and nesting components
第一个概念:什么是组件(component)?
React apps are made out of components. A component is a piece of the UI (user interface) that has its own logic and appearance. A component can be as small as a button, or as large as an entire page.
React components are JavaScript functions that return markup(React 组件是返回标签的 JavaScript 函数):
1 2 3 4 5 |
function MyButton() { return ( <button>I'm a button</button> ); } |
Now that you’ve declared MyButton
, you can nest it into another component(组件可以嵌套):
1 2 3 4 5 6 7 8 |
export default function MyApp() { return ( <div> <h1>Welcome to my app</h1> <MyButton /> </div> ); } |
Notice that <MyButton />
starts with a capital letter. That’s how you know it’s a React component. React component names must always start with a capital letter, while HTML tags must be lowercase.(React 组件命名必须用大写字母开头,HTML 标签命名必须用小写字符开头)
Have a look at the result :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function MyButton() { return ( <button> I'm a button </button> ); } export default function MyApp() { return ( <div> <h1>Welcome to my app</h1> <MyButton /> </div> ); } |
The export default
keywords specify the main component in the file(export default
指定了该组件是主组件).
Writing markup with JSX
The markup syntax you’ve seen above is called JSX. It is optional, but most React projects use JSX for its convenience.(上面例子中的语法叫做 JSX)。JSX is stricter than HTML. You have to close tags like <br />
. Your component also can’t return multiple JSX tags. You have to wrap them into a shared parent, like a <div>...</div>
or an empty <>...</>
wrapper(重点:一个组件不能返回多个标签,如果要返回多个标签,你可以用另外一个标签将它们包起来,如下所示):
1 2 3 4 5 6 7 8 |
function AboutPage() { return ( <> <h1>About</h1> <p>Hello there.<br />How do you do?</p> </> ); } |
上面的例子 return 返回的是一个 <> … </> 标签,不用大惊小怪,如果你不想在标签中增加一个额外的 <div>,可以用 <> 和 </> 元素来代替。
Adding styles (样式)
In React, you specify a CSS class with className
. It works the same way as the HTML class
attribute, You can write the CSS rules for it in a separate CSS file:(在 React 中给元素指定样式使用 React 特定的关键字 className
,你可以将样式放在单独的一个 CSS 文件中,然后使用 className
关键字给某个元素添加样式):
1 2 3 4 5 |
/* In your CSS */ .avatar { border-radius: 50%; } /* 使用 className 关键字给某个元素添加样式 */<img className="avatar" /> |
Displaying data (显示数据)
JSX lets you put markup into JavaScript. Curly braces let you “escape back” into JavaScript so that you can embed some variable from your code and display it to the user. For example, this will display user.name
(JXF 允许你将标签放到 JavaScript 中。而大括号会让你 “回到” JavaScript 中,这样你就可以从你的代码中嵌入一些变量并展示给用户。==》意思你可以在 JSF 语法中使用 js,只需要用大括号括起来。如下 {user.name} 会读取 js 中的 user 变量中的 name 属性。):
1 2 3 4 5 |
return ( <h1> {user.name} </h1> ); |
You can also “escape into JavaScript” from JSX attributes, but you have to use curly braces instead of quotes. For example, className="avatar"
passes the "avatar"
string as the CSS class, but src={user.imageUrl}
reads the JavaScript user.imageUrl
variable value, and then passes that value as the src
attribute(你还可以将 JSX 属性 “转义到 JavaScript”,但你必须使用大括号 而非 引号。例如,className="avatar"
是将 "avatar"
字符串传递给 className
,作为 CSS 的 class。但 src={user.imageUrl}
会读取 JavaScript 的 user.imageUrl
变量,然后将该值作为 src
属性传递:):
1 2 3 4 5 6 |
return ( <img className="avatar" src={user.imageUrl} /> ); |
神奇的大括号(Curly brace):一扇进入 JavaScript 世界的窗户
大括号内的任何 JavaScript 表达式都能正常运行,包括像 formatDate() 这样的函数调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const today = new Date(); function formatDate(date) { return new Intl.DateTimeFormat( 'zh-CN', { weekday: 'long' } ).format(date); } export default function TodoList() { return ( <h1>To Do List for {formatDate(today)}</h1> ); } |
可以在哪使用大括号?
在 JSX 中,只能在以下两种场景中使用大括号:
- 用作 JSX 标签内的文本:{name}’s To Do List 是有效的,但是 <{tag}>Gregorio Y. Zara’s To Do List 无效。
- 用作紧跟在 = 符号后的 属性:src={avatar} 会读取 avatar 变量,但是 src=”{avatar}” 只会传一个字符串 {avatar}。
使用双大括号
除了字符串、数字和其它 JavaScript 表达式,你甚至可以在 JSX 中传递对象。对象也用大括号表示,例如 { name: “Hedy Lamarr”, inventions: 5 }。因此,为了能在 JSX 中传递,你必须用另一对额外的大括号包裹对象:person={{ name: “Hedy Lamarr”, inventions: 5 }}。
所以当你下次在 JSX 中看到 {{ 和 }}时,就知道它只不过是包在大括号里的一个对象罢了!
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* 这是一个 js 变量,可以在 JSF 中通过大括号引起 js 变量的值 */ 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 }} /> </> ); } |
Conditional rendering (条件渲染)
In React, there is no special syntax for writing conditions. Instead, you’ll use the same techniques as you use when writing regular JavaScript code. For example, you can use an if
statement to conditionally include JSX(React 没有特殊的语法来编写条件语句,你可以使用 JavaScript 中的条件语句。例如使用 if
语句根据条件引入 JSX:):
1 2 3 4 5 6 7 8 9 10 11 |
let content; if (isLoggedIn) { content = <AdminPanel />; } else { content = <LoginForm />; } return ( <div> {content} </div> ); |
██ 强烈注意两点 ██:
(1)上面例子中,你的条件语句 if … else … 是写在 return ( … ) 外面的,在 return ( … ) 里面好像是没法使用 if … else … 条件语句的。
(2)你的 if … else … 是在写在大括号里面的!!!
更多条件语句写法示例:
If you prefer more compact code, you can use the conditional ?
operator. Unlike if
, it works inside JSX:
1 2 3 4 5 6 7 |
<div> {isLoggedIn ? ( <AdminPanel /> ) : ( <LoginForm /> )} </div> |
When you don’t need the else
branch, you can also use a shorter logical &&
syntax:
1 2 3 |
<div> {isLoggedIn && <AdminPanel />} </div> |
Rendering lists (渲染列表)
You will rely on JavaScript features like for
loop and the array map()
function to render lists of components.
For example, let’s say you have an array of products , you can use the map()
function to transform an array of products into an array of <li>
items :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const products = [ { title: 'Cabbage', id: 1 }, { title: 'Garlic', id: 2 }, { title: 'Apple', id: 3 }, ]; const listItems = products.map(product => <li key={product.id}> {product.title} </li> ); return ( <ul>{listItems}</ul> ); |
Notice how <li>
has a key
attribute. For each item in a list, you should pass a string or a number that uniquely identifies that item among its siblings. Usually, a key should be coming from your data, such as a database ID. React uses your keys to know what happened if you later insert, delete, or reorder the items.(上面例子使用 .map 遍历数组 products 时,使用了个叫 key 的属性,为什么会有这个 key 属性呢?是为了用 key 标记数组中每个元素的唯一性,所以你应该使用唯一的、不会重复的值作为 key 的值。)
Responding to events (响应事件)
You can respond to events by declaring event handler functions inside your components(你可以通过在组件中声明 事件处理 函数来响应事件):
1 2 3 4 5 6 7 8 9 10 11 12 |
function MyButton() { /* 事件处理函数 */ function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); } |
Notice how onClick={handleClick}
has no parentheses at the end! Do not call the event handler function: you only need to pass it down. React will call your event handler when the user clicks the button.(注意三点:第一,onClick={handleClick}
是用大括号包住 的。第二,handleClick
后面没有小括号。第三,你不需要主动调用这个事件处理函数,当有点击事件时,React 会自动调用。)
Updating the screen (更新界面)
Often(通常), you’ll want your component to “remember” some information and display it. For example, maybe you want to count the number of times a button is clicked. To do this, add state to your component.
First, import useState
from React:
1 |
import { useState } from 'react'; |
Now you can declare a state variable inside your component:
1 2 3 |
function MyButton() { const [count, setCount] = useState(0); // ... |
You’ll get two things from useState
: the current state (count
), and the function that lets you update it (setCount
). You can give them any names, but the convention is to write [something, setSomething]
.
The first time the button is displayed, count
will be 0
because you passed 0
to useState()
. When you want to change state, call setCount()
and pass the new value to it. Clicking this button will increment the counter:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function MyButton() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> Clicked {count} times </button> ); } |
如上,const [count, setCount] = useState(0);
里面的 count
是一个属性,默认值由 useState(0)
已经指定了是 0 ,而 setCount
是一个函数名,如果需要更新 count
的值需要调用这个 setCount
函数。==》这种语法和写法初看可能觉得有点奇怪,慢慢适应了就好了。
需要注意:If you render the same component multiple times, each will get its own state. Click each button separately:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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> ); } |
Notice how each button “remembers” its own count
state and doesn’t affect other buttons.
如上,<div> 中有两个 <MyButton /> 组件对象,则每个组件对象都有自己的 count 值。
Using Hooks (使用钩子)
Functions starting with use
are called Hooks. useState
is a built-in Hook provided by React. You can find other built-in Hooks in the API reference. You can also write your own Hooks by combining the existing ones.(以 use
开头的函数被称为 Hook。useState
是 React 提供的一个内置 Hook。)
Hooks are more restrictive than other functions. You can only call Hooks at the top of your components (or other Hooks). If you want to use useState
in a condition or a loop, extract a new component and put it there.(Hook 比普通函数更为严格。你只能在你的组件(或其他 Hook)的 顶层 调用 Hook。如果你想在一个条件或循环中使用 useState
,请提取一个新的组件并在组件内部使用它。)
Sharing data between components (在组件间交换数据)
In the previous example, each MyButton
had its own independent count
, and when each button was clicked, only the count
for the button clicked changed:
However, often you’ll need components to share data and always update together.
To make both MyButton
components display the same count
and update together, you need to move the state from the individual buttons “upwards” to the closest component containing all of them.(为了使得 MyButton
组件显示相同的 count
并一起更新,你需要将各个按钮的 state “向上” 移动到最接近包含所有按钮的组件之中。)
In this example, it is MyApp
:
Now when you click either button, the count
in MyApp
will change, which will change both of the counts in MyButton
. Here’s how you can express this in code.
First, move the state up from MyButton
into MyApp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
export default function MyApp() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <div> <h1>Counters that update separately</h1> <MyButton /> <MyButton /> </div> ); } function MyButton() { // ... we're moving code from here ... } |
Then, pass the state down from MyApp
to each MyButton
(将 state 从 MyApp 传递给 MyButton), together with the shared click handler(连同处理函数也一起从 MyApp 传递给 MyButton).
You can pass information to MyButton
using the JSX curly braces,(可以使用大括号向 MyButton 传递信息), just like you previously did with built-in tags like <img>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
export default function MyApp() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <div> <h1>Counters that update together</h1> {/* 注意:下面 {count}是将 count 值传递给 MyButton 组件 */} <MyButton count={count} onClick={handleClick} /> <MyButton count={count} onClick={handleClick} /> </div> ); } |
██注意██:上面代码 return ( … ) 里面的 <MyButton count={count} onClick={handleClick} />
里面的 {count}
是将 count 值传递给 MyButton 组件,onClick={handleClick}
是将 handleClick
处理函数传递给 MyButton 组件。==》MyButton 组件怎么接受这两个值呢?见下面代码:
1 2 3 4 5 6 7 |
function MyButton({ count, onClick }) { return ( <button onClick={onClick}> Clicked {count} times </button> ); } |
The information you pass down like this is called props (使用这种方式传递的信息被称作 props ). Now the MyApp
component contains the count
state and the handleClick
event handler, and passes both of them down as props to each of the buttons.(此时 MyApp
组件包含了 count
state 以及 handleClick
事件处理函数,并将它们作为 prop 传递给 了每个按钮。)
什么是 prop ?==》使用这种”从父组件往子组件”传递信息的方式,被称作 props
Finally, change MyButton
to read the props you have passed from its parent component(最后,改变 MyButton
以 读取 从父组件传递来的 prop:):
1 2 3 4 5 6 7 |
function MyButton({ count, onClick }) { return ( <button onClick={onClick}> Clicked {count} times </button> ); } |
When you click the button, the onClick
handler fires. Each button’s onClick
prop was set to the handleClick
function inside MyApp
, so the code inside of it runs. That code calls setCount(count + 1)
, incrementing the count
state variable. The new count
value is passed as a prop to each button, so they all show the new value. This is called “lifting state up”. By moving state up, you’ve shared it between components.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import { useState } from 'react'; export default function MyApp() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <div> <h1>Counters that update together</h1> <MyButton count={count} onClick={handleClick} /> <MyButton count={count} onClick={handleClick} /> </div> ); } function MyButton({ count, onClick }) { return ( <button onClick={onClick}> Clicked {count} times </button> ); } |