这篇教学文章涵盖了大量的React基本知识。
包括:
- 事件监听器
- Props和State的区分
- 改变state的方法
- 使用回调函数改变state
- 使用三元运算符改变state
- 处理state中的数组
- 处理state中的object
- 条件渲染 &&
- 条件渲染 三元运算符
- React中的forms
1. Event Listeners
在React中,事件监听器(Event Listeners)是用于响应用户操作(如点击、输入、鼠标移动等)的函数。
React中的事件监听器通常以camelCase形式命名,而不是使用HTML中的小写命名。例如,HTML中的onclick在React中写作onClick。
在React组件中,你可以在JSX中直接添加事件监听器。这些监听器是作为组件的属性添加的,其值是一个函数,当事件触发时,这个函数会被调用。
export default function App(){
function handleClick(){
console.log("I was clicked!")
}
function handleOnMouseOver (){
console.log("MouseOver")
}
return (
<div>
<img
src="https://picsum.photos/640/360"
onMouseOver={handleOnMouseOver}
/>
<button onClick={handleClick}>Click me</button>
</div>
)
}
2. Props vs. State
“Props” refers to the properties being passed into a component in order for it to work correctly, similar to how a function receives parameters. A component receiving props is not allowed to modify those props. (i.e. they are “immutable”)
“State” refers to values that are managed by the component, similar to variables declared inside a function. Anytime you have changing values that should be saved/displayed, you will likely be using state.
在React中,组件的数据管理主要通过两种属性来实现:props(属性)和state(状态)。理解它们之间的区别对于构建有效的React应用至关重要。
Props(属性)
- 不可变性:Props是从父组件传递到子组件的数据,它们是不可变的。这意味着一旦props被传递给组件,组件就不能直接修改这些props的值。如果需要改变props的值,必须通过父组件来完成。
- 单向数据流:Props支持React的单向数据流原则,即数据只能从父组件流向子组件,这有助于避免数据流的混乱和应用状态的不可预测性。
State(状态)
- 可变性:State是组件内部管理的数据,它可以在组件的生命周期内被改变。State的变化会触发组件的重新渲染。
- 组件内部管理:State是私有的,只能在声明它的组件内部被直接访问和修改。State的变化通常是由于用户交互或程序逻辑的需要。
- 触发渲染:当组件的state发生变化时,React会重新渲染组件,以确保UI与最新的状态保持同步。
3. Changing State
在React中,“changing state”(改变状态)是指更新组件内部的状态(state)。状态是React组件的私有数据,它可以帮助组件记录和响应用户操作或其他事件。当状态发生变化时,React会重新渲染组件,以确保UI与最新的状态保持同步。
改变状态的方法
在类组件中,状态通过this.setState()方法更新,在函数组件中,状态通过useState钩子(hook)的设置函数(setter function)更新。
类组件中改变状态
在类组件中,状态是使用this.state来声明的,而this.setState()方法用来更新状态。
函数组件中改变状态
在函数组件中,useState钩子允许你添加状态到函数组件中。useState返回一个数组,其中包含当前状态值和一个可以更新该状态的函数。
import React from "react"
export default function App(){
const [isImportant, setIsImportant] = React.useState("YES")
function handleClick(){
setIsImportant("NO")
}
return (
<div>
<h1>Is state important to know?</h1>
<div onClick={handleClick}>
<h1>{isImportant}</h1>
</div>
</div>
)
}
4. Changing state with a callback function
在React中,使用回调函数(callback function)来改变状态是一种常见的做法,尤其是在你需要基于当前状态来更新状态时。这种模式允许你在状态更新时立即获取最新的状态值,这对于执行依赖于前一个状态的计算特别有用。
import React from "react"
export default function App(){
const [count, setCount] = React.useState(0)
function add(){
setCount(prevCount => prevCount+1)
}
function subtract(){
setCount(prevCount => prevCount-1)
}
return (
<div>
<button onClick={subtract}>-</button>
<div>
<h1>{count}</h1>
</div>
<button onClick={add}>+</button>
</div>
)
}
5. Changing state: ternary
在React中,使用三元运算符(ternary operator)来改变状态是一种简洁的方式来根据条件选择新的状态值。三元运算符是一种表达式,它有三个操作数:一个条件、一个真值和一个假值。如果条件为真,则返回真值;如果条件为假,则返回假值。
import React from "react"
export default function App(){
const [isGoingOut, setIsGoingOut] = React.useState(true)
function changeMind(){
setIsGoingOut(prevState => !prevState)
}
return (
<div>
<h1>Do I feel like going out tonight?</h1>
<div onClick={changeMind}>
<h1>{isGoingOut ? "Yes" : "No"}</h1>
</div>
</div>
)
}
6. Complex State: arrays
在React中处理复杂状态,尤其是涉及到数组时,我们需要特别小心,因为数组是引用类型,直接修改数组不会触发组件的重新渲染。以下是一些关于如何在React中处理复杂状态,特别是数组的要点:
- 不要直接修改状态
直接修改数组(如通过索引赋值或使用push、pop等方法)不会触发组件的重新渲染。这是因为React使用浅比较来检测状态的变化。因此,任何对数组的修改都应该通过创建一个新数组来完成,以确保状态的变化能够被React检测到。 - 使用函数式更新
在函数组件中,当需要基于当前状态来更新数组时,应该使用函数式更新。这意味着传递一个函数给setState(或在useState中的设置函数),这个函数接收当前状态作为参数,并返回新的状态。
import React from "react"
export default function App(){
const [thingsArray, setThingsArray] = React.useState(["Thing 1", "Thing 2"])
function addItem(){
setThingsArray(prevState => {
return [...prevState, `Thing ${prevState.length + 1}`]
})
}
const thingsElements = thingsArray.map(thing => <p key={thing}>{thing}</p>)
return (
<div>
<button onClick={addItem}>Add Item</button>
{thingsElements}
</div>
)
}
在这个例子中,我们通过扩展运算符…来创建一个新数组,包含旧数组的所有元素以及新添加的元素,这样可以确保状态的变化。
7. Complex state: objects
在React中处理复杂状态,尤其是涉及到对象时,与处理数组类似,需要特别注意不可变性(immutability)。这是因为React依赖于比较状态的新旧值来决定是否重新渲染组件。如果直接修改对象,React可能无法检测到状态的变化,从而不会触发组件的重新渲染。
- 不要直接修改状态
直接修改对象(如添加、删除或修改对象的属性)不会触发组件的重新渲染。因此,任何对对象状态的修改都应该通过创建一个新对象来完成。 - 使用函数式更新
在函数组件中,当需要基于当前状态来更新对象时,应该使用函数式更新。这意味着传递一个函数给setState(或在useState中的设置函数),这个函数接收当前状态作为参数,并返回新的状态。
import React from "react"
export default function App(){
const [contact, setContact] = React.useState({
firstName: "John",
lastName: "Doe",
phone: "+1 (719) 555-1212",
email: "itsmyrealname@example.com",
isFavorite: true
})
let starIcon = contact.isFavorite ? "star-filled.png" : "star-empty.png"
function toggleFavorite(){
console.log("Toggle Favorite")
}
return (
<main>
<article>
<img src="images/user.png" />
<div>
<img
src={`images/${starIcon}`}
onClick={toggleFavorite}
/>
<h2>
{contact.firstName} {contact.lastName}
</h2>
<p>{contact.phone}</p>
<p>{contact.email}</p>
</div>
</article>
</main>
)
}
star-empty.png
star-filled.png
user.png
8. Complex state: updating state object
在这个例子中,我们通过扩展运算符…来创建一个新对象,包含旧对象的所有属性以及新修改的属性,这样可以确保状态的变化能够被React检测到。
import React from "react"
export default function App(){
const [contact, setContact] = React.useState({
firstName: "John",
lastName: "Doe",
phone: "+1 (719) 555-1212",
email: "itsmyrealname@example.com",
isFavorite: true
})
let starIcon = contact.isFavorite ? "star-filled.png" : "star-empty.png"
function toggleFavorite(){
setContact(prevContact => ({
...prevContact,
isFavorite: !prevContact.isFavorite
}))
}
return (
<main>
<article>
<img src="images/user.png" />
<div>
<img
src={`images/${starIcon}`}
onClick={toggleFavorite}
/>
<h2>
{contact.firstName} {contact.lastName}
</h2>
<p>{contact.phone}</p>
<p>{contact.email}</p>
</div>
</article>
</main>
)
}
9. Flip the box
App.jsx
import React from "react"
import boxes from "../public/components/boxes";
import Box from "../public/components/Box";
export default function App(){
const [squares, setSquares] = React.useState(boxes)
const squareElements = squares.map(square => (
<Box key={square.id} on={square.on} />
))
return (
<main>
{squareElements}
</main>
)
}
boxes.jsx
export default [
{
id: 1,
on: true
},
{
id: 2,
on: false
},
{
id: 3,
on: true
},
{
id: 4,
on: true
},
{
id: 5,
on: false
},
{
id: 6,
on: false
},
]
Box.jsx
import React from "react"
export default function Box(props) {
const [on, setOn] = React.useState(props.on)
const styles = {
backgroundColor: on ? "#222222" : "transparent"
}
function toggle(){
setOn(prevOn => !prevOn)
}
return (
<div
style={styles}
className="box"
onClick={toggle}
></div>
)
}
index.css
* {
box-sizing: border-box;
}
body {
font-family: "Karla", sans-serif;
margin: 0;
}
.box {
height: 100px;
width: 100px;
border: 1px solid black;
display: inline-block;
margin-right: 4px;
border-radius: 5px;
}
10. Flip the box (use ternary operator)
App.jsx
import React from "react"
import boxes from "../public/components/boxes";
import Box from "../public/components/Box";
export default function App(){
const [squares, setSquares] = React.useState(boxes)
function toggle(id) {
setSquares(prevSquares => {
return prevSquares.map((square) => {
return square.id === id ? {...square, on: !square.on} : square
})
})
}
const squareElements = squares.map(square => (
<Box
key={square.id}
on={square.on}
toggle={() => toggle(square.id)} />
))
return (
<main>
{squareElements}
</main>
)
}
Box.jsx
import React from "react"
export default function Box(props) {
const styles = {
backgroundColor: props.on ? "#222222" : "transparent"
}
return (
<div
style={styles}
className="box"
onClick={props.toggle}
></div>
)
}
11. Conditional rendering: &&
在React中,条件渲染是指根据某些条件来决定是否渲染某些组件或元素。这可以通过多种方式实现,包括使用JavaScript的逻辑与(&&)操作符。逻辑与操作符&&在条件渲染中非常有用,因为它允许你在同一个表达式中进行条件检查和元素渲染。
使用&&进行条件渲染
当你使用&&操作符时,如果其左边的表达式为真(truthy),则整个表达式的结果将取决于右边的表达式。在JSX中,这可以用来直接根据条件渲染元素。
import React from "react"
import boxes from "../public/components/boxes";
import Box from "../public/components/Box";
export default function App(){
const [messages, setMessages] = React.useState(["a", "b", "c"])
return (
<div>
{
messages.length > 0 &&
<h1>You have {messages.length} unread messages!</h1>
}
</div>
)
}
12. Conditional rendering: ternary
在React中,条件渲染是指根据条件来决定是否渲染某些组件或元素。三元运算符(ternary operator)是一种常用的JavaScript表达式,用于基于条件进行快速的“if-else”判断,它在React的条件渲染中也非常有用。
condition ? expressionIfTrue : expressionIfFalse;
- condition:一个布尔表达式,其结果为true或false。
- expressionIfTrue:如果condition为true,则返回这个表达式的值。
- expressionIfFalse:如果condition为false,则返回这个表达式的值。
你可以在JSX中直接使用三元运算符来决定渲染哪个元素或组件。
import React from "react"
import boxes from "../public/components/boxes";
import Box from "../public/components/Box";
export default function App(){
const [messages, setMessages] = React.useState(["a"])
return (
<div>
{
messages.length === 0 ?
<h1>You're all caught up!</h1> :
<h1>You have {messages.length} unread {messages.length > 1 ? "messages" : "message"}</h1>
}
</div>
)
}
13. Conditional rendering
-
What is “conditional rendering”?
When we want to only sometimes display something on the page
based on a condition of some sort -
When would you use &&?
When you want to either display something or NOT display it -
When would you use a ternary?
When you need to decide which thing among 2 options to display -
What if you need to decide between > 2 options on
what to display?
Use anif...else if... else
conditional or aswitch
statement
14. Watch for input changes in React
在React中,监视输入变化是一个常见的需求,尤其是在处理表单时。这可以通过几种方式实现,包括使用状态(state)和效果(effects)。
使用State和Effects监视输入变化
在React中,你可以通过维护一个状态来监视输入的变化。当输入变化时,更新这个状态,从而触发组件的重新渲染。
import React from "react"
export default function App(){
const [firstName, setFirstName] = React.useState("")
console.log(firstName)
function handleChange(event){
setFirstName(event.target.value)
}
return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
/>
</form>
)
}
在这个例子中,每次用户输入时,handleChange函数都会被调用,并更新firstName状态,这会导致组件重新渲染并显示最新的输入值。
15. Form state object
import React from "react"
export default function App(){
const [formData, setFormData] = React.useState(
{firstName: "", lastName: "", email: ""}
)
console.log(formData)
function handleChange(event){
setFormData(prevFormData => {
return {
...prevFormData,
[event.target.name]: event.target.value
}
})
}
return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
/>
</form>
)
}
16. Forms in React: textarea
import React from "react"
export default function App(){
const [formData, setFormData] = React.useState(
{firstName: "", lastName: "", email: "", comments: ""}
)
console.log(formData.comments)
function handleChange(event){
setFormData(prevFormData => {
return {
...prevFormData,
[event.target.name]: event.target.value
}
})
}
return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
value={formData.lastName}
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
value={formData.email}
/>
<textarea
value={formData.comments}
placeholder="Comments"
onChange={handleChange}
name="comments"
/>
</form>
)
}
17. Forms in React: checkbox
import React from "react"
export default function App(){
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true
}
)
function handleChange(event){
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
value={formData.lastName}
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
value={formData.email}
/>
<textarea
value={formData.comments}
placeholder="Comments"
onChange={handleChange}
name="comments"
/>
<input
type="checkbox"
id="isFriendly"
checked={formData.isFriendly}
onChange={handleChange}
name="isFriendly"
/>
<label htmlFor="isFriendly">Are you friendly?</label>
<br />
</form>
)
}
18. Forms in React: radio buttons
import React from "react"
export default function App(){
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true,
employment: ""
}
)
console.log(formData.employment)
function handleChange(event){
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
value={formData.lastName}
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
value={formData.email}
/>
<textarea
value={formData.comments}
placeholder="Comments"
onChange={handleChange}
name="comments"
/>
<input
type="checkbox"
id="isFriendly"
checked={formData.isFriendly}
onChange={handleChange}
name="isFriendly"
/>
<label htmlFor="isFriendly">Are you friendly?</label>
<br />
<br />
<fieldset>
<legend>Current employment status</legend>
<input
type="radio"
id="unemployed"
name="employment"
value="unemployed"
checked={formData.employment === "unemployed"}
onChange={handleChange}
/>
<label htmlFor="unemployed">Unemployed</label>
<br />
<input
type="radio"
id="part-time"
name="employment"
value="part-time"
checked={formData.employment === "part-time"}
onChange={handleChange}
/>
<label htmlFor="part-time">Part-time</label>
<br />
<input
type="radio"
id="full-time"
name="employment"
value="full-time"
checked={formData.employment === "full-time"}
onChange={handleChange}
/>
<label htmlFor="full-time">Full-time</label>
<br />
</fieldset>
</form>
)
}
19. Forms in React: Select and options
import React from "react"
export default function App(){
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true,
employment: "",
favColor: ""
}
)
console.log(formData.favColor)
function handleChange(event){
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
value={formData.lastName}
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
value={formData.email}
/>
<textarea
value={formData.comments}
placeholder="Comments"
onChange={handleChange}
name="comments"
/>
<input
type="checkbox"
id="isFriendly"
checked={formData.isFriendly}
onChange={handleChange}
name="isFriendly"
/>
<label htmlFor="isFriendly">Are you friendly?</label>
<br />
<br />
<fieldset>
<legend>Current employment status</legend>
<input
type="radio"
id="unemployed"
name="employment"
value="unemployed"
checked={formData.employment === "unemployed"}
onChange={handleChange}
/>
<label htmlFor="unemployed">Unemployed</label>
<br />
<input
type="radio"
id="part-time"
name="employment"
value="part-time"
checked={formData.employment === "part-time"}
onChange={handleChange}
/>
<label htmlFor="part-time">Part-time</label>
<br />
<input
type="radio"
id="full-time"
name="employment"
value="full-time"
checked={formData.employment === "full-time"}
onChange={handleChange}
/>
<label htmlFor="full-time">Full-time</label>
<br />
</fieldset>
<label htmlFor="favColor">What is your favorite color?</label>
<br />
<select
id="favColor"
value={formData.favColor}
onChange={handleChange}
name="favColor"
>
<option value="">-- Choose --</option>
<option value="red">Red</option>
<option value="orange">Orange</option>
<option value="yellow">Yellow</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="indigo">Indigo</option>
<option value="violet">Violet</option>
</select>
</form>
)
}
20. Forms in React: Submitting the form
import React from "react"
export default function App(){
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true,
employment: "",
favColor: ""
}
)
function handleChange(event){
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
function handleSubmit(event) {
event.preventDefault()
console.log(formData)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
value={formData.lastName}
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
value={formData.email}
/>
<textarea
value={formData.comments}
placeholder="Comments"
onChange={handleChange}
name="comments"
/>
<input
type="checkbox"
id="isFriendly"
checked={formData.isFriendly}
onChange={handleChange}
name="isFriendly"
/>
<label htmlFor="isFriendly">Are you friendly?</label>
<br />
<br />
<fieldset>
<legend>Current employment status</legend>
<input
type="radio"
id="unemployed"
name="employment"
value="unemployed"
checked={formData.employment === "unemployed"}
onChange={handleChange}
/>
<label htmlFor="unemployed">Unemployed</label>
<br />
<input
type="radio"
id="part-time"
name="employment"
value="part-time"
checked={formData.employment === "part-time"}
onChange={handleChange}
/>
<label htmlFor="part-time">Part-time</label>
<br />
<input
type="radio"
id="full-time"
name="employment"
value="full-time"
checked={formData.employment === "full-time"}
onChange={handleChange}
/>
<label htmlFor="full-time">Full-time</label>
<br />
</fieldset>
<label htmlFor="favColor">What is your favorite color?</label>
<br />
<select
id="favColor"
value={formData.favColor}
onChange={handleChange}
name="favColor"
>
<option value="">-- Choose --</option>
<option value="red">Red</option>
<option value="orange">Orange</option>
<option value="yellow">Yellow</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="indigo">Indigo</option>
<option value="violet">Violet</option>
</select>
<br />
<br />
<button>Submit</button>
</form>
)
}
21. Sign up form practice
import React from "react"
export default function App(){
const [formData, setFormData] = React.useState({
email: "",
password: "",
passwordConfirm: "",
joinedNewsletter: true
})
function handleChange(event){
const {name, value, type, checked} = event.target
setFormData(prevFormData => ({
...prevFormData,
[name]: type === "checkbox" ? checked : value
}))
}
function handleSubmit(event){
event.preventDefault()
if (formData.password === formData.passwordConfirm){
console.log("Successfully signed up")
} else {
console.log("Passwords do not match")
return
}
if(formData.joinedNewsletter){
console.log("Thanks for signing up for our newsletter!")
}
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="Email address"
name="email"
onChange={handleChange}
value={formData.email}
/>
<input
type="password"
placeholder="Password"
name="password"
onChange={handleChange}
value={formData.password}
/>
<input
type="password"
placeholder="Confirm password"
name="passwordConfirm"
onChange={handleChange}
value={formData.passwordConfirm}
/>
<div>
<input
id="okayToEmail"
type="checkbox"
name="joinedNewsletter"
onChange={handleChange}
checked={formData.joinedNewsletter}
/>
<label htmlFor="okayToEmail">I want to join the newsletter</label>
</div>
<button>Sign up</button>
</form>
</div>
)
}