React学习笔记
React 入门
Intro 介绍
CDN、导包
CDN:
1 | <script |
导包,无需使用 CDN:
1 | import React from "react"; |
特点
Composable 可组合的
我们有一些小的代码碎片,我们可以把它们拼在一起,组成更大的东西。
Declarative 声明的
声明性意味着我可以告诉计算机要做什么,并期望它处理细节。
渲染
ReactDOM.render(<html代码>,<在哪渲染>)
例如:
1 | import React from "react"; |
或者(React 18 新语法)
1 | ReactDOM.createRoot(<在哪渲染>).render( |
例如:
1 | import React from "react"; |
如果将 <html 代码> 定义到变量里,最外层只能是父节点一个,可以是<div> ... </div>
也可以写成空标签<> ... </>
1 | const page = ( |
JSX 内部的 JS
括号() 里写 HTML 代码
花括号{} 里的代码可以写 JS 语法
1 | function App() { |
Note: 如果想在 HTML 里加入 JS 逻辑代码,可以加上花括号{},但只能写表达式,不能写赋值等语句。
Component: function
Custom Components 自定义组件
1 | import React from "react" |
更简洁的写法
1 | const TemporaryName = () => ( |
Note:组件名要大写
什么是 React 组件?
返回 React 元素 的函数。(UI)
Parent/Child Components 父子组件
形如:
1 | function Children() { |
Styling with Classes 修饰类
类名不再是像 JS 里的 class 了,而是 className
1 | <h1 className="name">Harris</h1> |
把 JSX 里的代码当做 HTML,CSS 代码直接在 .css 文件里写,和往常一样
Organize components 整理组件
除了将代码按功能或者区域拆分成许多代码块还不够,代码量更大的时候,会考虑分离出一个文件出来。那么这时如何创建“组件文件”呢?且如何引用组件呢?
“组件文件”:
1 | import React from "react" |
引用组件:
1 | import React from "react" |
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 | import React from "react" |
1 | <Addition a="1" b="2" /> |
可以看到,引用组件的同时,a、b 属性值将被传到 Addition 函数里的 props 参数里,并以对象的方式保存。通过 props.<属性名>
可以在函数里调用组件传来的值。
组件和函数里的 props 名称可以随意取,但函数里的参数最好还是写 props。
img 里的 src 可以像这样调用数据:
1
2
3 <img src={props.img}/>
或类似于
<img src={`../images/${props.img}`} />
${}
这个符号能使我们在字符串里传入变量原生 HTML 标签的 style 可以这样调用数据:
<p style={{display: props.name ? "block" : "none"}}>{props.name}</p>
。这里注意是 2 个花括号,因为里面包含 JS 里的三元表达式(ternary expression),所以要再加一个花括号,当然了,style 里的值本身就是一个对象,所以需要花括号。
Props 的作用:
What do props help us accomplish?
Make a component more reusable.
另一种 props 取值方式
1 | export default function Contact({title, content} { |
Passing in non-string props 传入非字符串类的 Props
1 | <Student |
由此可见,传入非字符串需要用到花括号 {}
prop: key
记得组件里得带上一个 key 属性,值一般是 id 之类唯一的值,否则控制台会有一个 warning
推荐使用 nanoid 包,会随机产生一个 id
Pass object as props
这样不需要在调用组件时写太多数据传输的相关代码
1 | const students = data.map((item) => { |
1 | export default function Students(props) { |
Spread object as props 将对象作为 props 展开
这样写代码更简洁
1 | const students = data.map((item) => { |
1 | export default function Students(props) { |
对比一下之前的繁杂写法:
1 | const students = data.map(item => { |
props.children
除了下面这种写法可以传值
<Students name="Harris", sex="Male">
还有如下方式,可以将 html 代码通过 props.children
传入组件里
1 | <Students> |
1 | export default function Students(props) { |
.map()
一般这里使用.map()
是为了批量将数据加工成为可视化代码,例如:
1 | const students = studentData.map((student) => { |
小问答:
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.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.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.
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 | export default function App() { |
更多: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 | const isMale = React.useState("Yes"); |
f()
指的是一个 function,通过这个 function 咋们可以改变 state 的值
用 Array destructuring 数组解构这种方式:
1 | const [isMale, func] = React.useState("Yes"); |
注意:是
[isMale, func]
而不是{isMale, func}
,中括号[ ]!
Changing state
1 | const [isMale, setIsMale] = React.useState("Yes"); |
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 | function add() { |
Complex state: arrays 数组
改数组有些麻烦
以 To-do list 为例,这用到了 ES6 新语法:
1 | const [thingsArray, setThingsArray] = React.useState(["Thing 1", "Thing 2"]); |
Complex state: objects 对象
以学生信息为例:
1 | const [stuInfo, setStuInfo] = React.useState({ |
先将所有信息传入,再写要更改的属性。
Lazy State Initialization
有时候 state 的不断改变,会导致整个组件重复被加载,有些代码可能加载一次很费网络资源,而且也不需要加载,这时候需要设置 Lazy State Initialization(惰性状态初始化)
例如:
1 | const [state, setState] = React.useState(() => |
也就是设置 callback function。像上面这个例子,控制台输出一次该语句,就不用再重复输出了。
Sharing data between components 组件间共享数据
有时候子组件之间需要互传值怎么办?这时需要 raise state up to parent component.
在 React 里,数据只能从父组件下传到子组件,所以要想在子组件间共享某一个数值,则需要提前将该数据放在子组件共有且最近的父组件里。
Dynamic styles 动态样式
有时候一个组件或者元素的样式需要动态变化,那么需要做判断以决定用什么样式
1 | const styles = { |
这里要强调的是 style 里的属性不再是 CSS 里的那种,要写成驼峰式命名法 camel case
Conditional rendering 条件渲染
顾名思义,就是满足特定条件了,才能渲染指定内容
&& logical and operator 逻辑与运算符
When you want to either display something or NOT display it
以显示句子为例:
1 | function toggleShown() { |
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 语句,得在外边写
Form 表单
onChange
onChange:一个改变 value 就会触发的方法(表单常用)
以表单输入学生信息为例:
1 | function handleChange() { |
event
表单元素里方法被触发后,可以通过接受 event 参数 来获得表单里的值。
例子同上:
1 | function handleChange(event) { |
event.target
里面除了 value 之外,还有其它常用属性:
1 | function handleChange(event) { |
state object
有时候表单元素很多,一个一个写 handleChange 比较麻烦,这时候需要 object 来统一存储表单里的值。
例子同上:
1 | const [formData, setFormData] = React.useState({ firstName: "", lastName: "" }); |
Application: Checkbox
例子同上:
1 | function handleChange(event) { |
其中设置 id 是为了 htmlFor 能够定向到,即点击文字也可以选中按钮
Application: Radio buttons
例子同上:
1 | function handleChange(event) { |
Application: Select & Option
例子同上:
1 | <label htmlFor="favColor">What is your favorite subject?</label> |
Application: Submitting the form
1 | function handleSubmit(event) { |
<button>
默认是type="submit"
,故这里可省略。event.preventDefault()
作用是阻止页面刷新,这样我们就可以正常运行handleSubmit
方法。你可以理解成这是提交表单信息时必写的句子。
Side effects 副作用
useEffect()
有时候我们希望一些方法在组件渲染后运行,这时可以将代码写进
useEffect()
里,这样可以消除无限循环调用的副作用。比如请求数据等。
React.useEffect(func, [param1, parma2...])
有两个参数:
func
是必填参数(方法),params
是可选参数(依赖,数组形式)
例如,当 count
的值变化了,则运行方法里的代码:
1 | React.useEffect( |
Note:
如果依赖里留空,则在组件第一次加载完毕后运行一次,之后不再运行。
适合写 setInterval、请求数据等:
1
2
3
4
5
6 useEffect(() => {
const intervalId = setInterval(() => {
setCount(prevCount => prevCount + 1)
}, 1000)
return () => clearInterval(intervalId)
}, [])在某些情况下,我们可能需要在组件卸载时清除副作用,这时可以在
useEffect
中 return 一个函数,这个函数会在组件卸载时执行,用于清除在useEffect
中设置的副作用,比如取消请求或取消订阅事件等。上例就能说明。
useEffect
可以在一个组件里写多个。
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 | React.useEffect(() => { |
Component: Class
这是比较过时的概念,Bob Ziroll 希望我们也学习了解一下
index.js
1 | import React from "react"; |
App.js
1 | import React from "react"; |
与 Function 组件 不同点是它多了一个
this.
,并且无需声明 props 参数 就可以直接通过this.props
调用
State
看以下两种的区别
Function:
1 | import React from "react"; |
Class:
1 | import React from "react"; |
Constructor method
1 | import React from "react"; |
Lifecycle methods: componentDidMount()
当组件挂载后再运行的 function
Lifecycle methods: componentDidUpdate()
当组件更新后再运行的 function
为了防止循环调用:
1 | componentDidUpdate(prevProps, prevState) { |
当目前的数据和之前的不一样时,以此表明数据更新了,于是再来调用 function,否则会循环调用。
Lifecycle methods: componentWillUnmount()
在组件即将卸载前运行的 function
例如,在组建卸载时去除事件监听器(解绑事件):
1 | componentWillUnmount() { |
Lifecycle methods: shouldComponentUpdate()
父组件更新会引起子组件的更新,有时子组件不需要更新,那么可以使用该钩子函数 shouldComponentUpdate(nextProps, nextState)
1 | class Hello extends Component { |
React 进阶
课程里最新 API:https://swapi.dev/api/people/1/
<React.Fragment>
它是一个虚拟容器,用来包含其它元素,而不会在渲染中对实际的 DOM 元素产生任何影响。
1 | function MyComponent() { |
React 16.2 及更高版本中引入了简写语法,允许使用空标签 <></>
来表示 Fragment,而不是使用完整的语法 <React.Fragment></React.Fragment>
Props
Default Props
有时组件没有传入 props,这时需要写默认值
方法 1,定义<Conponent>.defaultProps
:
1 | function Card(props) { |
方法 2:
1 | function Card(props) { |
Prop Types
有时 prop 值需要类型来限制/定义一下,防止组件传了错误类型的值。
1 | import PropTypes from "prop-types" |
Render Props
它是通过将渲染函数作为组件的 props 传递的方式。它允许一个组件将其 UI 渲染代码传递给另一个组件,以便在其上进行操作。
例如,假设有一个需要显示当前鼠标位置的组件,它可以使用 render props 将鼠标位置信息传递到 UI 上:
1 | const MouseTracker = (props) => { |
Higher Order Components (HOCs) 高阶组件
可以让您重用组件的逻辑,并对组件进行自定义扩展。
比如给组件提供横向滚动的功能:
1 | const withHorizontalScroll = (WrappedComponent) => { |
Performance Optimization
React.PureComponent
有时候父组件发生了状态或值的更改,其所有后代组件都会被迫渲染一次,很费时,将原来的
Component
换成React.PureComponent
即可解决问题。
React.PureComponent
只用于 Class 组件。
1 | import React, {PureComponent} from "react" |
React.memo()
可用于 Function 组件,类似于高阶组件的用法。
1 | function GrandParent() { |
或
1 | export default React.memo(function Parent() { |
Context
Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props。
Context Provider
Context Provider 相当于上下文的提供者,提供给组件使用共享的数据。它通常写在最低共同父组件的层级,这样既能方便相应组件访问 Context,也能准确控制 Context 数据被使用范围。
1 | const ThemeContext = React.createContext() |
contextType
contextType 更像是组件接收 context 的容器,通过
this.context
来取值,但只有 Class 组件可以用。
下例是切换深浅模式功能:
Button.js
1 | import ThemeContext from "./themeContext" |
index.js
1 | import ThemeContext from "./themeContext" |
Context Consumer
它更像是给 Function 组件使用的。
1 | import ThemeContext from "./themeContext" |
Move Context Provider to its own component
把上下文提供者写成组件更好,代码细节应该封装到一个组件里,也方便写 State。
ThemeContext.js
1 | import React from "react" |
Note:
value={"light"}
记得写上花括号 {},因为这只接收 object 形式
index.js
1 | import React from "react" |
Button.js
1 | import React from "react" |
Changing Context
获取上下文的值后,经常还需要更改它。
ThemeContext.js
1 | import React, {Component} from "react" |
注意:
<Provider>
组件的 props 只能是 value,如果要传入多个变量,得写成 Object 形式。
Button.js
1 | import React from "react" |
Hooks⭐️
React 内置的 Hooks 包括
useState
,useEffect
,useContext
,useRef
,useReducer
(常用)
useMemo
,useCallback
,useImperativeHandle
,useLayoutEffect
,useDebugValue
.你也可以自定义 Hooks。
useRef
useRef
的主要的功能就是帮助我们获取到DOM元素或者组件实例,它还可以保存在组件生命周期内不会变化的值。有时候希望点了按钮后,聚焦仍在输入框里,这时可以用
useRef
。
下例是一个 Todo list,我只保留了与 useRef 相关代码,看看如何使用就好:
1 | const inputRef = useRef(null) |
ref={inputRef}
意思是 inputRef 变量指向该 input 输入框
inputRef.current.focus()
意思是聚焦于该变量指向的输入框(直接敲键盘可以输入,无需鼠标点击)
useReducer
If you wanna use
useState()
hook to manage a non-trivial(重要的)state like a list of items, where you need to add, update and remove items in the state. You end up facing a situation where state management logic takes a good part of the component body. To help you separate the concerns (rendering and state management) React provides theuseReducer()
hook. (跟 Redux 里的 Reducer 真的很像耶)
Syntax: const [state, dispatch] = useReducer(reducer, initialState);
initialState
In the case of a counter state, the initial value could be like:
1 | const initialState = { |
Note: the
initialState
can be a simple value but generally will contain an object.
reducer
The reducer
function contains your custom state logic.
2 parameters: the current state and an action object.
1 | function reducer(state, action) { |
action object
action object describes how to update the state. The property type
describes what kind of state update the reducer must do. If the action object must carry some useful information (aka payload) to be used by the reducer, then you can add additional properties to the action object. Like user
below.
1 | const action = { |
dispatch
The dispatch
is a special function that dispatches an action object.
Syntax: dispatch(actionObject)
useContext
通过
useContext
拿到上下文里的数据。之前介绍过,这里直接上例子,看看和之前写的有什么不同(React进阶/Context/Move…),使用useContext
代码会更简洁。
ThemeContext.js
1 | import React, { useState } from "react"; |
index.js(保持不变)
1 | import React from "react" |
Button.js
1 | import React from "react" |
Custom Hooks
自定义 Hook 是一种让你能够将组件里可复用的逻辑提取到函数中的方式。自定义 Hook 可以让你在不增加组件的情况下,复用组件之间的状态逻辑,从而使代码更加简洁、清晰。
与 Component 直观区别是,Hook 是复用 JS 逻辑,Component 是复用 HTML 代码。
useCounter.js
1 | import {useState} from "react" |
Note:
return [count, increment]
部分也可以写成return {count, increment}
,这样的话,在接收时就必须使用该变量名了。
App.js
1 | import React, {useState} from "react" |
Router
React 路由 可以有效管理多个视图(组件)实现 SPA(single page application,单页应用)
Installation 安装
npm add react-router-dom
下面笔记是根据 V6 版本写的
<BrowserRouter> 与 <HashRouter>
在最外层包裹整个应用,它相当于 context
<BrowserRouter> :
1 | import React from "react" |
BrowserRouter模式,访问地址:
https://<url>/xxx
<HashRouter> :
1 | import React from "react" |
HashRouter模式,访问地址:
https://<url>/#/xxx
两者对比:
- hash模式兼容好,history模式是 HTML5 提出的,兼容性差(移动端兼容性不错)
- hash模式下,访问不存在的页面,不需要单独配置nginx。而history模式需要。
- history模式路由更好看;hash模式带#号,不美观,而且处理描点链接不方便。
<Routes> & <Route>
<Routes>
它用来包裹 <Route>
<Route>
相当于 if 语句,配置路由规则和要展示的组件(路由出口),可以嵌套使用,需要通过<Outlet/>
组件来渲染子路由;
1 | import { Routes, Route } from "react-router-dom"; |
<Link> 与 <NavLink>
<Link>
类似于a标签的,是基于a标签封装的组件。 最终<Link>
组件会转为a标签。通常应用就是在不同页面之间跳转。
有三种传参方式:
- params
- searchParams
- state
必须被<BrowserRouter / > 或 <HashRouter / > 包裹
1 | import { Link } from "react-router-dom"; |
<NavLink>
组件和<Link>
组件使用一致,但它相当于激活版的<Link>
组件,可以实现导航被选中的高亮效果。通常的使用场景是在同一个界面,tab栏切换,导航高亮。
1 | import { NavLink } from "react-router-dom"; |
State 参数
state 是可选参数,通常在不同页面之间传递数据时,可以将数据隐式存储在 state 中,然后在目标页面中访问它们。它好处是不会将数据暴露在URL中,也不会被出现在浏览器历史记录里,从而保持URL的简洁性、美观性和保护用户隐私。
<Navigate>
<Navigate>
组件被渲染,路径被修改,切换视图。它和<Link>
不同之处是,前者是渲染后跳转,后者需要点击事件发生才会跳转。
1 | import { Navigate } from "react-router-dom"; |
<Outlet />
当 route 发生嵌套的时候,必须使用
<Outlet />
组件来渲染子路由对应的视图,相当于子路由渲染组件的占位符。注意:它不能向即将要渲染的组件传值,它只用于显示组件。
1 | import { Routes, Route, Outlet } from "react-router-dom"; |
useRoutes() 🔥
接收数组生成路由表,动态创建 Routes 和 Route。类似 vue-router, 通过 js 配置路由。
Router.jsx
1 | // 这里也可以定义纯js,定义router.js配置路由,类似vue中那样配置路由。 |
App.js
1 | import { RouteList } from "./Router.jsx" |
useNavigate()
它和
history.push()
类似,语法会更简洁些,React Router v6 版本建议使用它进行路由跳转。通过
useNavigate()
返回的参数navigate
是一个函数。
1 | import { useNavigate } from "react-router-dom" |
useParams()
有时需要获取路由传来的值
App.js(定义路由)
1 | <Routes> |
ServicesList.js(链接传值)
1 | function ServicesList() { |
ServiceDetail.js(用 useParams
获取链接值,serviceId这个变量名一定要与router里的名称对应)
1 | import {useParams} from "react-router-dom" |
useSearchParams()
读取和修改当前位置的 URL 中的 query 对象
返回包含俩值的数组,内容分别为:当前的 search参数、更新search的函数
App.js
1 | import { useSearchParams } from "react-router-dom" |
detail.jsx(访问路由https://<url>/list/1
)
1 | import { useSearchParams } from "react-router-dom" |
用了 set 方法后也只是生成了新的 url,要访问该它还得写一个页面跳转方法
useRouteMatch()
有时需要更改路由路径名称,那其全部子路由需要手动更改了,很麻烦,这时需要用
useRouteMatch
,有了useRoutes()
就不需要用这个了。
1 | import {Link, Routes, Route, useRouteMatch} from "react-router-dom" |
Note: 官方文档建议
url
写在<Link/>
里,path
写在<Route/>
里。
useHistory()
有时要用 JS 代码实现页面跳转。但建议用
useNavigate()
。
使用 history.push("/pathname")
可跳转到路径为 /pathname
页面
1 | import {useHistory} from "react-router-dom" |
例子里的 history
是 React 路由提供的,用于获取浏览器历史记录的相关信息,还有很多用法:
history.goBack()
:回退到上个页面
history.goForward()
:前进到下个页面
history.go(int)
:前进或回退到某个页面,参数 int 表示前进或后退页面数量(例如:-1 表示回退到上一页)
history.replace(/pathname)
: 替换当前页面历史记录,这样可以禁止用户回退到上一页面。
useLocation()
有时需要获取当前 URL 的位置信息,包括路径名 pathname、搜索参数 search、状态值 state 等等
1 | import { useLocation } from 'react-router-dom'; |
如果在搜索框里输入
/list?name=harris&age=18
useOutlet()
在嵌套路由中使用,父组件使用
<Outlet />
, 当子组件渲染的时候,会展示嵌套路由的对象。如果嵌套路由没有挂载,返回是null。
useOutlet()
相较于<Outlet />
的好处是:
- 可获取子路由相关信息
- 可定制化子路由的渲染逻辑
1 | import { useOutlet } from 'react-router-dom'; |
useOutletContext()
Often parent routes manage state or other values you want to be shared with child routes. You can create your own context provider if you like, but this is such a common situation that it's built-into
<Outlet />
.也就是用它可以向子路由组件传值。
Parent.jsx
1 | function Parent() { |
Child.jsx
1 | import { useOutletContext } from "react-router-dom"; |
Redirect 重定向
有时候需要页面重定向,比如检测是否登陆,若无就重定向到登陆界面。
语法:<Redirect to="/pathname" />
这是 React Router v4版本,v6 版本推荐用<Navigate />
,关键还能传递参数和状态等信息。
1 | import { Navigate } from 'react-router-dom'; |
Styled Components
Install
npm install --save styled-components
Advantages
- Automatic Critical CSS: Keep track of which components are rendered.
- No Class name bugs: Styled-components generate unique class names for your styles which means no duplicates or overlaps.
- Easy to find CSS: Styled-components make it super easy to find your CSS, as every bit of styling is tied to a specific component.
- Dynamic Styling: Adapting the styling of a component based on its props or a global theme.
- Automatic Vendor Prefixing: Write CSS the way you know and love, and let the package do it's magic.
Syntax
1 | import styled from 'styled-components' |
Styling through Props
1 | import styled from 'styled-components' |
color: ${({color}) => color}
中的({})
最外层括号可有可无,中间的花括号是解构赋值语法,你用 props来获取值也可以。比如:
color: ${props => props.color}
JSON Server
Since we are focusing on the UI at the moment, we need to set up a dummy server(虚拟服务器), which will allow us to test our requests.
We can use json-server tool to create a full RESTful API from a JSON file.
JSON Server is a simple project that helps you to setup a REST API with CRUD operations very fast.
Creating the db.json
To be able to use the json-server
tool, first we need to create a db.json
file, which is going to contain our full database for the server.
The json-server
tool will allow you to make the following:
GET requests, to fetch data from the file.
POST requests, to insert new data into the file.
PUT and PATCH requests, to adjust existing data.
DELETE requests, to remove data.
Let's create src/server/db.json
.
1 | { |
We need to make sure that we provide an
id
value so that we can query the database later.
Installing the json-server tool
We're gon' install the json-server tool via npm:
npm install --save json-server
Now, we can start our backend server by calling the following command:
npx json-server --watch server/db.json
The
--watch
flag means that it'll listen to changes to the files, and refresh automatically.
Output
Now, we can go to http://localhost:3000/posts/1
to see our post object.
The result will be like this:
1 | { |
Configuring the package.json
We need to change the port, so that it doesn't run on the same port as our client.
We need to install a tool called concurrently
, which lets us start the server and client at the same time:
npm install --save concurrently
package.json
1 | "scripts":{ |
Now, running npm start
will run the client, as well as the backend server.
Configuring a proxy
Finally, we have to define a proxy(代理), to make sure that we can request our API from the same Uniform Resource Locator (URL) as the client.
This is needed because, otherwise, we would have to deal with cross-site requests, which are a bit more complicated to handle.
We are going to define a proxy that will forward requests from
http://localhost:3000/api/
tohttp://localhost:4000/
.
Now, let's configure the proxy:
npm install --save http-proxy-middleware
Then, we create a new
src/setupProxy.js
file:1
2
3
4
5
6
7
8const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(proxy('/api', {
target: 'http://localhost:4000',
pathRewrite: {'^/api': ''}
}))
}target
: the target of our proxy is the backend server.pathRewrite
: It'll remove the/api
prefix before forwarding the request to our server.
The preceding proxy configuration will link /api
to our backend server, therefore, we can now start both the server and the client via npm start
.
Then, we can access the API by opening http://localhost:3000/posts/1
.
技巧合集
快捷代码指令插件
在 VSCode 里安装ES7+ React/Redux/React-Native snippets
插件后,可以通过键入rafce
快捷创建一个 function 组件
项目
目录结构
通常,src 目录结构如下:
1 | src/ 项目源码,写项目功能代码 |
components/
里放一些公用 UI 组件,包括导航栏和页尾等
containers/
里放需要与数据和状态有关的 UI 组件,负责从数据层获取数据,处理用户交互,然后传递数据到 UI 组件进行呈现。
Project Setup 项目安装 1
这里使用的是 Vite,它是一种新型前端构建工具,能够显著提升前端开发体验
1 | npm create vite@latest |
或者 👇
Project Setup 项目安装 2
初始化项目:npx create-react-app <project-name>
启动项目:
- Development mode:
npm run dev
- Production mode:
npm start
部署项目
将 React 项目部署到 GitHub 和 Vercel 需要以下步骤:
部署到 GitHub
在GitHub上创建一个新的repository,并将你的代码推送到这个repository中。
确保你的代码已经经过了所有的测试,并且已经成功运行过。
在你的React项目中安装GitHub Pages插件:
npm install gh-pages --save-dev
打开
package.json
文件,添加以下代码到文件中:1
2
3
4
5"homepage": "https://<your-username>.github.io/<your-repository-name>",
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
}注意:
homepage
中的URL应该与你的GitHub repository相对应。运行以下命令将你的应用程序部署到 GitHub Pages:
1
2npm run build
npm run deploy
部署到 Vercel
- 首先,你需要创建一个 Vercel 账户,并登录到 Vercel 控制台。
- 在控制台中,点击“New Project”,并选择“Import Git Repository”选项,将你的GitHub repository导入到Vercel中。
- 确保你的项目中有一个
package.json
文件,并在其中设置start
脚本。例如:
1 | "scripts": { |
- 点击“Deploy”,等待Vercel构建和部署你的应用程序。
- 一旦部署成功,你的应用程序将会自动在Vercel上运行。
实用工具库
Ant Design
安装
npm install antd --save
自定义主题 & 深浅切换
自定义主题在官网可以自己定制主色、尺寸等等,然后复制代码到 token 这里。
1 | import { theme, ConfigProvider, Button } from "antd"; |
MUI
安装
npm install @mui/material @emotion/react @emotion/styled
自定义主题 & 深浅切换
可以用 mui-theme-creator 帮助设计和自定义 MUI 组件库主题,还可以用 Material palette generator 为您输入的任何颜色生成调色板。
1 | import { ThemeProvider, createTheme } from "@mui/material"; |
自定义主题组件
您可以使用主题中的键来自定义组件的样式、默认props等。这有助于在整个应用程序中实现样式的一致性。
echarts-for-react
这是一个简单的 Apache echarts 的 React 封装。
npm install --save echarts-for-react
react-icons
npm install react-icons