我看的是React教学视频 - Scrimba

React 初级

Intro 介绍

CDN、导包

CDN:

1
2
3
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

导包,无需使用CDN:

1
2
import React from "react"
import ReactDOM from "react-dom"

特点

1.Composable 可组合的

我们有一些小的代码碎片,我们可以把它们拼在一起,组成更大的东西。

2.Declarative 声明的

声明性意味着我可以告诉计算机要做什么,并期望它处理细节。

渲染

1
2
3
ReactDOM.render(<html代码>,<在哪渲染>)
例如:
ReactDOM.render(<h1>Harris</h1>, document.getElementById("root"))

或者(React 18 新语法)

1
2
3
ReactDOM.createRoot(<在哪渲染>).render(
<代码>
)

例如:

1
2
3
4
import React from "react"
import ReactDOM from "react-dom/client"

ReactDOM.createRoot(document.getElementById("root")).render(<h1>Harris</h1>)

如果将 <html代码> 定义到变量里,最外层只能是父节点一个,可以是<div> ... </div>也可以是空标签<> ... </>

1
2
3
4
5
const page = (
<div>
[html代码]
</div>
)

JSX 内部的 JS

花括号{}里的代码可以写JS语法

1
2
3
4
5
6
7
8
9
function App() {
const firstName = "Harris"
const lastName = "Wong"
return (
<h1>Hello {firstName} {lastName}!</h1>
)
}

// Hello Harris Wong!

Component: function

Custom Components 自定义组件

1
2
3
4
5
6
7
8
9
10
import React from "react"
import ReactDOM from "react-dom"

function TemporaryName() {
return (
[代码]
)
}

ReactDOM.render(<TemporaryName />, document.getElementById("root"))

Note:组件名要大写

什么是 React 组件?

返回 React元素 的函数。(UI)

Parent/Child Components 父子组件

形如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Children() {
return (
<代码>
)
}

function Father() {
return (
<div>
<Children />
<代码>
</div>
)
}

Styling with Classes 修饰类

类名不再是像JS里的 class 了,而是 className

1
<h1 className="name">Harris</h1>

把 JSX 里的代码当做 HTML,CSS代码直接在 .css 文件里写,和往常一样

Organize components 整理组件

除了将代码按功能或者区域拆分成许多代码块还不够,代码量更大的时候,会考虑分离出一个文件出来。那么这时如何创建“组件文件”呢?且如何引用组件呢?

“组件文件”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react"

export default function Header() {
return (<代码>)
}

// 或者

import React from "react"

function Header() {
return (<代码>)
}

export default Header

引用组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react"
import ReactDOM from "react-dom"
import Header from "./Header" (可以不带上后缀.js)

function Page() {
return (
<div>
<Header />
</div>
)
}

ReactDOM.render(<Page />, document.getElementById("root"))

Props 属性

为了完成组件之间数据的通信,引入了 Props 概念

"Props" refers to the properties being passed into a component in order for it to work correctly, similar to how a function receives parameters: "from above." A component receiving props is not allowed to modify those props. (l.e. they are "immutable(不可变的)")

例如,下面是一个加法器案例:

1
2
3
4
5
import React from "react"

export default function Addition(props) {
return {props.a} + {props.b}
}
1
<Addition a="1" b="2" />

可以看到,引用组件的同时,a、b属性值将被传到 Addition 函数里的 props 参数里,并以对象的方式保存。通过 props.<属性名>可以在函数里调用组件传来的值。

  1. 组件和函数里的 props 名称可以随意取,但函数里的参数最好还是写 props。

  2. img 里的 src 可以像这样调用数据:

    1
    2
    3
    <img src={props.img}/>
    或类似于
    <img src={`../images/${props.img}`} />

    ${}这个符号能使我们在字符串里传入 props

  3. 原生 HTML 标签的 style 可以这样调用数据:<p style={{display: props.name ? "block" : "none"}}>{props.name}</p>。这里注意是2个花括号,因为里面包含JS里的三元表达式(ternary expression),所以要再加一个花括号。

Props 的作用

What do props help us accomplish?

Make a component more reusable.

另一种 props 取值方式

1
2
3
4
5
6
7
8
export default function Contact({title, content} {
return (
<div>
<h1>{title}</h1>
<p>{content}</p>
</div>
)
}

Passing in non-string props 传入非字符串类的 Props

1
2
3
4
5
6
<Student 
name="Harris"
DOB={2000.08}
isMale={true}
grade={{Chinese:88,Math:96,English:98}}
/>

由此可见,传入非字符串需要用到花括号

.map()

一般这里使用.map()是为了批量将数据加工成为可视化代码,例如:

1
2
3
const students = studentData.map(student => {
return <Student name={student.name} sex={student.sex} />
})

小问答

  1. What does the .map() array method do?
    Returns a new array. Whatever gets returned from the callback
    function provided is placed at the same index in the new array.
    Usually we take the items from the original array and modify them
    in some way.
  1. What do we usually use .map() for in React?
    Convert an array of raw data into an array of JSX elements
    that can be displayed on the page.
  1. Why is using .map() better than just creating the components
    manually by typing them out?
    It makes our code more "self-sustaining" - not requiring
    additional changes whenever the data changes.

key prop

记得组件里得带上一个 key 属性,值一般是 id 之类唯一的值,否则控制台会有一个 warning

推荐使用 nanoid 包,会随机产生一个 id

Pass object as props

这样不需要在调用组件时写太多数据传输的相关代码

1
2
3
4
5
6
7
8
const students = data.map(item => {
return (
<Students
key={item.id}
item={item}
/>
)
})
1
2
3
export default function Students(props) {
//用props.item来调用
}

Spread object as props 将对象作为 props 展开

这样写代码更简洁

1
2
3
4
5
6
7
8
const students = data.map(item => {
return (
<Students
key={item.id}
{...item}
/>
)
})
1
2
3
export default function Students(props) {
//还是用props来调用
}

对比一下之前的繁杂写法:

1
2
3
4
5
6
7
8
9
10
11
const students = data.map(item => {
return (
<Students
key={item.id}
name={item.name}
age={item.age}
sex={item.sex}
...
/>
)
})

Static Web Pages 静态Web页面

  • Read-only, no changes to data
  • Examples
    • Blogs
    • News sites
    • etc.

Dynamic Web Apps 动态Web应用程序

  • Read-write: ability to change data
  • Highly interactive
  • Displays your data

Event listeners 事件侦听器

An event listener is a procedure in JavaScript that waits for an event to occur. A simple example of an event is a user clicking the mouse or pressing a key on the keyboard.

1
2
3
4
5
6
7
8
9
10
11
export default function App() {
function handleClick() {
console.log("I was clicked!")
}

return (
<>
<button onClick={handleClick}>Click me</button>
</>
)
}

更多:Mouse Events 鼠标事件(95%的事件侦听事件都和 鼠标事件 有关)

State

我们编码时发现 function 运行后无法更改组件里的变量,这时需引入 state 概念。

"State" refers to values that are managed by the component, similar to variables declared inside a function. Any time you have changing values that should be save/displayed, you'll likely be using state.

1
2
3
4
5
6
7
8
9
const isMale = React.useState("Yes")
return (
<div className="state">
<div className="state--value">
<h1>{isMale[0]}</h1>
</div>
</div>
)
// isMale打印后:{"Yes", f()}

f()指的是一个 function,通过这个 function 咋们可以改变 state 的值

用 Array destructuring 数组解构这种方式:

1
2
3
4
5
6
7
8
const [isMale, func] = React.useState("Yes")
return (
<div className="state">
<div className="state--value">
<h1>{isMale}</h1>
</div>
</div>
)

Changing state

1
2
3
4
5
6
7
8
9
10
11
12
13
const [isMale, setIsMale] = React.useState("Yes")

function handleClick() {
setIsMale("No")
}

return (
<div className="state">
<div className="state--value" onClick={handleClick}>
<h1>{isMale}</h1>
</div>
</div>
)

Callback function 回调函数

If you ever need the old value of state to help you determine the new value of state, you should pass a callback function to your state setter function instead of using state directly. This callback function will receive the old value of state as its parameter, which you can then use to determine your new value of state.

言简意赅,你若想更改 state 值,建议采用回调函数

以加法函数为例,采用回调函数:

1
2
3
function add() {
setCount(prevCount => prevCount + 1)
}

Complex state: arrays 数组

改数组有些麻烦

以To-do list为例,这用到了ES6新语法:

1
2
3
4
5
const [thingsArray, setThingsArray] = React.useState(["Thing 1", "Thing 2"])

function addItem() {
setThingsArray(prevThingsArray => [...prevThingsArray, `Thing ${prevThingsArray.length}`])
}

Complex state: objects 对象

以学生信息为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const [stuInfo, setStuInfo] = React.useState({
firstName: "Harris",
lastName: "Wong",
isMale: true
})

function toggleIsMale() {
setContact(prevStuInfo => {
return {
...prevStuInfo,
isMale: !prevStuInfo.isMale
}
})
}
//或者,toggleIsMale()还可以这么写:
function toggleIsMale() {
setContact(prevStuInfo => ({
...prevStuInfo,
isMale: !prevStuInfo.isMale
}))
}

先将所有信息传入,再写要更改的属性。

Lazy State Initialization

有时候 state 的不断改变,会导致整个组件重复被加载,有些代码可能加载一次很费网络资源,而且也不需要加载,这时候需要设置 Lazy State Initialization(惰性状态初始化)

例如:

1
2
3
const [state, setState] = React.useState(
() => console.log("Lazy State Initialization")
)

也就是设置 callback function。像上面这个例子,控制台输出一次该语句,就不用再重复输出了。

Sharing data between components 组件间共享数据

有时候子组件之间需要互传值怎么办?这时需要 raise state up to parent component.

在 React 里,数据只能从父组件下传到子组件,所以要想在子组件间共享某一个数值,则需要提前将该数据放在子组件共有且最近的父组件里。

Dynamic styles 动态样式

有时候一个组件或者元素的样式需要动态变化,那么需要做判断以决定用什么样式

1
2
3
4
5
6
7
const styles = {
backgroundColor: props.darkMode ? "#222222" : "#cccccc"
}

const squareElements = squares.map(square => (
<div style={styles} className="box" key={square.id}></div>
))

这里要强调的是 style 里的属性不再是 CSS 里的那种,要写成驼峰式命名法 camel case

Conditional rendering 条件渲染

顾名思义,就是满足特定条件了,才能渲染指定内容

&& logical and operator 逻辑与运算符

When you want to either display something or NOT display it

以显示句子为例:

1
2
3
4
5
6
7
8
9
10
function toggleShown(){
setIsShown(prevShown => !prevShown)
}
return (
<div>
{isShown && <p>{props.sentence}</p>}
<button onClick={toggleShown}>Show Sentence</button>
<hr />
</div>
)

ternary expression

When you need to decide which thing among 2 options to display

例子同上:

1
<button onClick={toggleShown}>{isShown ? "Hide" : "Show"} Sentence</button>

if else

When you need to decide between > 2 options on what to display

记住 return() 里的 {} 不能写 if else 语句,得在外边写

Forms

onChange

onChange:一个改变 value 就会触发的方法

以表单输入学生信息为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
function handleChange() {
console.log("Changed!")
}

return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
/>
</form>
)

event

表单元素里方法被触发后,可以通过接受 event参数 来获得表单里的值

例子同上:

1
2
3
function handleChange(event) {
console.log(event.target.value)
}

event.target里面除了 value 之外,还有其它常用属性:

1
2
3
4
5
6
7
8
9
10
function handleChange(event) {
// 我们可以像下面这样接收
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: value
}
})
}

state object

有时候表单元素很多,一个一个写 handleChange 比较麻烦,这时候需要 object 来统一存储表单里的值

例子同上:

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
27
28
29
30
31
32
const [formData, setFormData] = React.useState(
{firstName: "", lastName: ""}
)

function handleChange(event) {
const {name, value} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: 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}
/>
</form>
)

Application: Checkbox

例子同上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function handleChange(event) {
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}

<input
type="checkbox"
id="isMale"
checked={formData.isMale}
onChange={handleChange}
name="isMale"
/>
<label htmlFor="isMale">Are you male?</label>

其中设置 id 是为了 htmlFor 能够定向到,即点击文字也可以选中按钮

Application: Radio buttons

例子同上:

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
27
28
29
30
31
32
33
function handleChange(event) {
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: value
}
})
}

<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>

<input
type="radio"
id="employed"
name="employment"
value="employed"
checked={formData.employment === "employed"}
onChange={handleChange}
/>
<label htmlFor="employed">Employed</label>
</fieldset>

Application: Select & Option

例子同上:

1
2
3
4
5
6
7
8
9
10
11
12
<label htmlFor="favColor">What is your favorite subject?</label>
<select
id="favSubject"
value={formData.favSubject}
onChange={handleChange}
name="favSubject"
>
<option value="">-- Choose --</option>
<option value="chinese">Chinese</option>
<option value="english">English</option>
<option value="math">Math</option>
</select>

Application: Submitting the form

1
2
3
4
5
6
7
8
9
10
11
function handleSubmit(event) {
event.preventDefault()
console.log(formData)
}

return (
<form onSubmit={handleSubmit}>
[html代码]
<button>Sumbit</button>
</form>
)
  1. <button>默认是type="submit",故这里可省略
  2. event.preventDefault()作用是阻止页面刷新,这样我们就可以正常运行handleSubmit方法

Side effects 副作用

useEffect()

有时候我们希望一些方法随着组件内一些内容的更新而在组件加载完毕后运行,这时可以将代码写进 useEffect()

React.useEffect()有两个参数,第一个是必填参数(方法),第二个是可选参数(依赖,数组形式)

例如:

1
2
3
React.useEffect(function() {
console.log("Effect ran")
}, [count])

此例子的意思是,当 count 的值变化了,则运行方法里的代码

Tip:如果依赖里没写变量,则在组件第一次加载完毕后运行一次,之后不再运行

Using an async function inside useEffect(Clone)

useEffect takes a function as its parameter. If that function returns something, it needs to be a cleanup function. Otherwise, it should return nothing. If we make it an async function, it automatically retuns a promise instead of a function or nothing. Therefore, if you want to use async operations inside of useEffect, you need to define the function separately inside of the callback function, as seen below:

1
2
3
4
5
6
7
8
React.useEffect(() => {
async function getMemes() {
const res = await fetch("https://api.imgflip.com/get_memes")
const data = await res.json()
setAllMemes(data.data.memes)
}
getMemes()
}, [])

Component: Class

index.js

1
2
3
4
5
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"

ReactDOM.render(<App type="Class" />, document.getElementById("root"))

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react"

// export default function App(props) {
// return (
// <h1>{props.type} component</h1>
// )
// }

export default class App extends React.Component {
render() {
return (
<h1>{this.props.type} component</h1>
)
}
}

与function组件不同的是this.

State

看以下两种的区别

function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react"

export default function App() {
const [goOut, setGoOut] = React.useState("Yes")

function toggleGoOut() {
setGoOut(prevState => {
return prevState === "Yes" ? "No" : "Yes"
})
}

return (
<div className="state">
<h1 className="state--title">Should I go out tonight?</h1>
<div className="state--value" onClick={toggleGoOut}>
<h1>{goOut}</h1>
</div>
</div>
)
}

class:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React from "react"

export default class App extends React.Component {
/**
* A class component with state will ALWAYS save state in a class
* instance variable called `state`, which will always be an object.
* The individual values you save in state will be properties on
* the `state` object.
*
* The simplest (and more modern) way to delcare new state in a
* class component is to just use a "class field" declaring state
* as an object, like you see below.
*
* Then, throughout the rest of the component (e.g. inside the render
* method) you can access that state with `this.state.<yourPropertyHere>`
*/
state = {
goOut: "Yes"
}

/**
* Any class methods you create that need to call the `this.setState`
* method (which is available to our component because we're extending
* React.Component) should be declared as an arrow function, for
* reasons we will discuss soon. (Note: other class methods you
* want to make that DON'T use `this.setState` don't necessarily
* need to be declared as arrow function to work correctly)
*/
toggleGoOut = () => {
this.setState(prevState => ({goOut: prevState.goOut === "Yes" ? "No" : "Yes"}))
}

render() {
return (
<div className="state">
<h1 className="state--title">Should I go out tonight?</h1>
<div className="state--value" onClick={this.toggleGoOut}>
<h1>{this.state.goOut}</h1>
</div>
</div>
)
}
}

Constructor method

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React from "react"

export default class App extends React.Component {
/**
* If you can't use class fields in your class components
* for some reason, then you'll need to make use of the
* class' `constructor` method to initialize your state object.
* The first line of the constructor method should be a call
* to `super()` like you see below, and then you can add your
* state variable as a property attached to `this`
*/
constructor(props) {
super(props)
this.state = {
goOut: "Yes"
}
this.toggleGoOut = this.toggleGoOut.bind(this)
}

/**
* If you can't use arrow functions for your class methods,
* you'll need to make sure to `bind` them inside the
* constructor above.
*/
toggleGoOut() {
this.setState(prevState => {
return {
goOut: prevState.goOut === "Yes" ? "No" : "Yes"
}
})
}

render() {
return (
<div className="state">
<h1 className="state--title">Should I go out tonight?</h1>
<div className="state--value" onClick={this.toggleGoOut}>
<h1>{this.state.goOut}</h1>
</div>
</div>
)
}
}

React 进阶

其它

Project Setup 项目安装

这里我们使用 Vite,它是一种新型前端构建工具,能够显著提升前端开发体验

1
2
3
4
npm create vite@latest
y
react
javascript

启动一个项目

初始化项目:npx create-react-app <project-name>

启动项目:npm start

src目录结构如下:

1
2
3
4
5
6
7
8
src/					项目源码,写项目功能代码
assets/ 资源(图片、字体等)
components/ 公共组件
pages/ 页面
utils/ 工具
App.js 根组件(配置路由信息)
index.css 全局样式
index.js 项目入口文件(渲染根组件、导入组件库)

安装antd-mobile组件库:npm add antd-mobile

样式无需自行导入,因为新版组件库已帮你导入好

配置基础路由:

  1. 安装:npm add react-router-dom

  2. 导入路由组件到App.js:Router / Route / Link

    import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";

  3. 创建页面文件到pages文件夹,比如Home/index.js

  4. 配置路由

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 导入组件
    import Home from "./pages/Home";
    import CityList from "./pages/CityList";

    function App() {
    return (
    <Router>
    <div className="App">
    {/* 配置路由 */}
    <Routes>
    <Route path="/home" element={<Home />}></Route>
    <Route path="/citylist" element={<CityList/>}></Route>
    </Routes>
    </div>
    </Router>
    );
    }

☕欲知后事如何,

且听下回分解🍵