Basic React
Intro
CDN、导包
CDN:
html < script crossorigin src = "https://unpkg.com/react@17/umd/react.development.js" / >
< 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:
js import React from "react" ;
import ReactDOM from "react-dom" ;
特点
Composable 可组合的
我们有一些小的代码碎片,我们可以把它们拼在一起,组成更大的东西。
Declarative 声明的
声明性意味着我可以告诉计算机要做什么,并期望它处理细节。
渲染
ReactDOM.render(<html代码>,<在哪渲染>)
例如:
jsx import React from "react" ;
import ReactDOM from "react-dom" ;
ReactDOM. render (< h1 >Harris</ h1 >, document. getElementById ( "root" ));
或者(React 18 新语法)
jsx ReactDOM. createRoot (< 在哪渲染 >).render(
< 代码 >
)
例如:
jsx import React from "react" ;
import ReactDOM from "react-dom/client" ;
ReactDOM. createRoot (document. getElementById ( "root" )). render (< h1 >Harris</ h1 >);
如果将 <html 代码>
定义到变量里,最外层只能是父节点一个,可以是<div> ... </div>
也可以写成空标签<> ... </>
jsx const page = (
< div >
< html代码 >
</ div >
)
JSX 内部的 JS
括号() 里写 HTML 代码
花括号 里的代码可以写 JS 语法
plaintext function App() {
const firstName = "Harris"
const lastName = "Wong"
return (
<h1>Hello {firstName} {lastName}!</h1>
)
}
// Hello Harris Wong!
Note: 如果想在 HTML 里加入 JS 逻辑代码,可以加上花括号,但只能写表达式,不能写赋值等语句。
Component: function
Custom Components 自定义组件
jsx import React from "react"
import ReactDOM from "react-dom"
function TemporaryName () {
return (
< html代码 >
)
}
ReactDOM.render(< TemporaryName />, document.getElementById("root"))
更简洁的写法
jsx const TemporaryName = () => (
< html代码 >
)
Note:组件名要大写
什么是 React 组件?
返回 React 元素 的函数。(UI)
Parent/Child Components 父子组件
形如:
jsx function Children () {
return (
< html代码 >
)
}
function Father() {
return (
< div >
< Children />
< html代码 >
</ div >
)
}
Styling with Classes 修饰类
类名不再是像 JS 里的 class 了,而是 className
plaintext <h1 className="name">Harris</h1>
把 JSX 里的代码当做 HTML,CSS 代码直接在 .css 文件里写,和往常一样
Organize components 整理组件
除了将代码按功能或者区域拆分成许多代码块还不够,代码量更大的时候,会考虑分离出一个文件出来。那么这时如何创建“组件文件”呢?且如何引用组件呢?
“组件文件”:
jsx import React from "react"
export default function Header () {
return (< html代码 >)
}
// 或者
import React from "react"
function Header() {
return (< html代码 >)
}
export default Header
引用组件:
jsx 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(不可变的)")
例如,下面是一个加法器案例:
jsx import React from "react"
export default function Addition ( props ) {
return {props.a} + {props.b}
}
jsx < Addition a = "1" b = "2" />
可以看到,引用组件的同时,a、b 属性值将被传到 Addition 函数里的 props 参数里,并以对象的方式保存。通过 props.<属性名>
可以在函数里调用组件传来的值。
组件和函数里的 props 名称可以随意取,但函数里的参数最好还是写 props。
img 里的 src 可以像这样调用数据:
jsx < 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 取值方式
jsx export default function Contact ({ title , content } {
return (
< div >
< h1 >{ title }</ h1 >
< p >{ content }</ p >
</ div >
)
}
Passing in non-string props
jsx < Student
name = "Harris"
DOB = { 2000.08 }
isMale = { true }
grade = {{ Chinese: 88 , Math: 96 , English: 98 }}
/>
由此可见,传入非字符串需要用到花括号
prop: key
记得组件里得带上一个 key 属性,值一般是 id 之类唯一的值,否则控制台会有一个 warning
推荐使用 nanoid 包,会随机产生一个 id
Pass object as props
这样不需要在调用组件时写太多数据传输的相关代码
jsx const students = data. map (( item ) => {
return < Students key = {item.id} item = {item} />;
});
jsx export default function Students ( props ) {
//用props.item来调用
}
Spread object as props 将对象作为 props 展开
这样写代码更简洁
jsx const students = data. map (( item ) => {
return < Students key = {item.id} { ... item} />;
});
jsx export default function Students ( props ) {
//还是用props来调用,但相比上一个例子,可少写props后面的“.item”
}
对比一下之前的繁杂写法:
jsx const students = data. map ( item => {
return (
< Students
key = {item.id}
name = {item.name}
age = {item.age}
sex = {item.sex}
...
/>
)
})
props.children
除了下面这种写法可以传值
<Students name="Harris", sex="Male">
还有如下方式,可以将 html 代码通过 props.children
传入组件里
jsx < Students >
< h1 >Harris</ h1 >
< h1 >Male</ h1 >
</ Students >
jsx export default function Students ( props ) {
return < div >{props.children}</ div >;
}
map()
一般这里使用.map()
是为了批量将数据加工成为可视化代码,例如:
jsx const students = studentData. map (( student ) => {
return < Student name = {student.name} sex = {student.sex} />;
});
小问答 :
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
Read-only, no changes to data
Examples
Dynamic Web Apps
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.
jsx 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.
jsx 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 数组解构这种方式:
jsx const [ isMale , func ] = React. useState ( "Yes" );
return (
< div className = "state" >
< div className = "state--value" >
< h1 >{isMale}</ h1 >
</ div >
</ div >
);
注意 :是[isMale, func]
而不是{isMale, func}
,中括号[ ]!
Changing state
jsx 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 值,建议采用回调函数
以加法函数为例,采用回调函数:
jsx function add () {
setCount (( prevCount ) => prevCount + 1 );
}
Complex state: arrays 数组
改数组有些麻烦
以 To-do list 为例,这用到了 ES6 新语法:
jsx const [ thingsArray , setThingsArray ] = React. useState ([ "Thing 1" , "Thing 2" ]);
function addItem () {
setThingsArray (( prevThingsArray ) => [
... prevThingsArray,
`Thing ${ prevThingsArray . length }` ,
]);
}
Complex state: objects 对象
以学生信息为例:
jsx 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(惰性状态初始化)
例如:
jsx 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
有时候一个组件或者元素的样式需要动态变化,那么需要做判断以决定用什么样式
jsx 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
以显示句子为例:
jsx 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
例子同上:
jsx < 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 语句,得在外边写
onChange
onChange:一个改变 value 就会触发的方法(表单常用)
以表单输入学生信息为例:
jsx function handleChange () {
console. log ( "Changed!" );
}
return (
< form >
< input type = "text" placeholder = "First Name" onChange = {handleChange} />
</ form >
);
event
表单元素里方法被触发后,可以通过接受 event 参数 来获得表单里的值 。
例子同上:
jsx function handleChange ( event ) {
console. log (event.target.value);
}
event.target
里面除了 value 之外,还有其它常用属性:
jsx function handleChange ( event ) {
// 我们可以像下面这样接收
const { name , value , type , checked } = event.target;
setFormData (( prevFormData ) => {
return {
... prevFormData,
[name]: value,
};
});
}
state object
有时候表单元素很多,一个一个写 handleChange 比较麻烦,这时候需要 object 来统一存储表单里的值。
例子同上:
jsx 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
例子同上:
jsx 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 能够定向到,即点击文字也可以选中按钮
例子同上:
jsx 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
例子同上:
jsx < 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 >
jsx function handleSubmit ( event ) {
event. preventDefault ();
console. log (formData);
}
return (
< form onSubmit = {handleSubmit}>
[html代码]
< button >Sumbit</ button >
</ form >
);
<button>
默认是type="submit"
,故这里可省略。
event.preventDefault()
作用是阻止页面刷新,这样我们就可以正常运行handleSubmit
方法。你可以理解成这是提交表单信息时必写的句子。
Side effects
useEffect()
有时候我们希望一些方法在组件渲染后运行,这时可以将代码写进 useEffect()
里,这样可以消除无限循环调用的副作用。比如请求数据等。
React.useEffect(func, [param1, parma2...])
有两个参数:
func
是必填参数(方法),params
是可选参数(依赖,数组形式)
例如,当 count
的值变化了,则运行方法里的代码:
jsx React. useEffect (
function () {
console. log ( "Effect ran" );
},
[count]
);
Note :
如果依赖里留空,则在组件第一次加载完毕后运行一次,之后不再运行。
适合写 setInterval、请求数据等:
jsx 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:
jsx 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
这是比较过时的概念,Bob Ziroll 希望我们也学习了解一下
index.js
jsx import React from "react" ;
import ReactDOM from "react-dom" ;
import App from "./App" ;
ReactDOM. render (< App type = "Class" />, document. getElementById ( "root" ));
App.js
jsx 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.
,并且无需声明 props 参数 就可以直接通过this.props
调用
State
看以下两种的区别
Function:
jsx 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:
jsx 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
jsx 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 >
);
}
}
Lifecycle methods: componentDidMount()
当组件挂载后再运行的 function
Lifecycle methods: componentDidUpdate()
当组件更新后再运行的 function
为了防止循环调用:
jsx componentDidUpdate (prevProps, prevState) {
if (prevState.data !== this .state.data) {
this . getData ( this .state.data)
}
}
当目前的数据和之前的不一样时,以此表明数据更新了,于是再来调用 function,否则会循环调用。
Lifecycle methods: componentWillUnmount()
在组件即将卸载前运行的 function
例如,在组建卸载时去除事件监听器(解绑事件):
jsx componentWillUnmount () {
window. removeEventListener ( 'mousemove' , this .handleMouseMove)
}
Lifecycle methods: shouldComponentUpdate()
父组件更新会引起子组件的更新,有时子组件不需要更新,那么可以使用该钩子函数 shouldComponentUpdate(nextProps, nextState)
jsx class Hello extends Component {
shouldComponentUpdate () {
//根据条件,决定是否重新渲染组件
return false
}
render () { ... }
}
Advanced React
课程里最新 API:https://swapi.dev/api/people/1/
<React.Fragment>
它是一个虚拟容器,用来包含其它元素,而不会在渲染中对实际的 DOM 元素产生任何影响。
jsx function MyComponent () {
return (
< React.Fragment >
< h1 >My Title</ h1 >
< ul >
< li >Item 1</ li >
< li >Item 2</ li >
< li >Item 3</ li >
</ ul >
</ React.Fragment >
);
}
React 16.2 及更高版本中引入了简写语法,允许使用空标签 <></>
来表示 Fragment,而不是使用完整的语法 <React.Fragment></React.Fragment>
Props
Default Props
有时组件没有传入 props,这时需要写默认值
方法 1,定义<Conponent>.defaultProps
:
jsx function Card ( props ) {
const styles = {
backgroundColor: props.cardColor,
height: props.height,
width: props.width,
};
return < div style = {styles}></ div >;
}
Card.defaultProps = {
cardColor: "blue" ,
height: 100 ,
width: 100 ,
};
方法 2:
jsx function Card ( props ) {
const styles = {
backgroundColor: props.cardColor || "black" ,
height: props.height || 100 ,
width: props.width || 100 ,
};
return < div style = {styles}></ div >;
}
有时 prop 值需要类型来限制/定义一下,防止组件传了错误类型的值。
jsx import PropTypes from "prop-types"
Card.propTypes = {
cardColor: PropTypes.string.isRequired
height: PropTypes.number
}
Render Props
它是通过将渲染函数作为组件的 props 传递的方式。它允许一个组件将其 UI 渲染代码传递给另一个组件,以便在其上进行操作。
例如,假设有一个需要显示当前鼠标位置的组件,它可以使用 render props 将鼠标位置信息传递到 UI 上:
jsx const MouseTracker = ( props ) => {
const [ position , setPosition ] = React. useState ({ x: 0 , y: 0 });
const handleMouseMove = ( event ) => {
setPosition ({
x: event.clientX,
y: event.clientY,
});
};
return < div onMouseMove = {handleMouseMove}>{props. render (position)}</ div >;
};
const App = () => {
return (
< div >
< h1 >Move the mouse around!</ h1 >
< MouseTracker
render = {({ x , y }) => (
< p >
The mouse position is ({x}, {y})
</ p >
)}
/>
</ div >
);
};
React.PureComponent
有时候父组件发生了状态或值的更改,其所有后代组件都会被迫渲染一次,很费时,将原来的Component
换成React.PureComponent
即可解决问题。
React.PureComponent
只用于 Class 组件。
jsx import React, { PureComponent } from "react" ;
class Child extends PureComponent {
render () {
console. log ( "[ ] [ ] [🧒🏻] [ ] rendered" );
return (
< div >
< p >I'm a Child Component</ p >
< GrandChild />
</ div >
);
}
}
React.memo()
可用于 Function 组件,类似于高阶组件的用法。
jsx function GrandParent () {
console. log ( "[👴🏼] [ ] [ ] [ ] rendered" );
return (
< div >
< p >I'm a GrandParent Component</ p >
< Parent />
</ div >
);
}
export default React. memo (GrandParent);
或
jsx export default React. memo ( function Parent () {
console. log ( "[ ] [👩] [ ] [ ] rendered" );
return (
< div >
< p >I'm a Parent Component</ p >
< Child />
</ div >
);
});
Context
Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props。
Context Provider
Context Provider 相当于上下文的提供者,提供给组件使用共享的数据。它通常写在最低共同父组件的层级,这样既能方便相应组件访问 Context,也能准确控制 Context 数据被使用范围。
jsx const ThemeContext = React. createContext ();
ReactDOM. render (
< ThemeContext.Provider >
< App />
</ ThemeContext.Provider >,
document. getElementById ( "root" )
);
contextType
contextType 更像是组件接收 context 的容器,通过this.context
来取值,但只有 Class 组件可以用。
下例是切换深浅模式功能:
Button.js
jsx import ThemeContext from "./themeContext" ;
class Button extends Component {
//写法1,仅下面1行
static contextType = ThemeContext;
render () {
// console.log(this.context)
return (
< button
className = { this .context == "light" ? "light-theme" : "dark-theme" }
>
Switch Theme
</ button >
);
}
}
//写法2
//Button.contextType = ThemeContext
index.js
jsx import ThemeContext from "./themeContext" ;
ReactDOM. render (
< ThemeContext.Provider value = { "dark" }>
< App />
</ ThemeContext.Provider >,
document. getElementById ( "root" )
);
Context Consumer
它更像是给 Function 组件使用的。
jsx import ThemeContext from "./themeContext" ;
function Button ( props ) {
return (
< ThemeContext.Consumer >
{( theme ) => < button className = { `${ theme }-theme` }>Switch Theme</ button >}
</ ThemeContext.Consumer >
);
}
Move Context Provider to its own component
把上下文提供者写成组件更好,代码细节应该封装到一个组件里,也方便写 State。
ThemeContext.js
jsx import React from "react" ;
const { Provider , Consumer } = React. createContext ();
const ThemeContextProvider = ( props ) => {
return < Provider value = { "light" }>{props.children}</ Provider >;
};
export { ThemeContextProvider, Consumer as ThemeContextConsumer };
Note:value={"light"}
记得写上花括号 ,因为这只接收 object 形式
index.js
jsx import React from "react" ;
import ReactDOM from "react-dom" ;
import App from "./App" ;
import { ThemeContextProvider } from "./themeContext" ;
ReactDOM. render (
< ThemeContextProvider >
< App />
</ ThemeContextProvider >,
document. getElementById ( "root" )
);
Button.js
jsx import React from "react" ;
import { ThemeContextConsumer } from "./themeContext" ;
function Button ( props ) {
return (
< ThemeContextConsumer >
{( theme ) => < button className = { `${ theme }-theme` }>Switch Theme</ button >}
</ ThemeContextConsumer >
);
}
export default Button;
Changing Context
获取上下文的值后,经常还需要更改它。
ThemeContext.js
jsx import React, { Component } from "react" ;
const { Provider , Consumer } = React. createContext ();
class ThemeContextProvider extends Component {
state = {
theme: "dark" ,
};
toggleTheme = () => {
this . setState (( prevState ) => {
return {
theme: prevState.theme === "light" ? "dark" : "light" ,
};
});
};
render () {
return (
< Provider
value = {{ theme: this .state.theme, toggleTheme: this .toggleTheme }}
>
{ this .props.children}
</ Provider >
);
}
}
export { ThemeContextProvider, Consumer as ThemeContextConsumer };
注意:<Provider>
组件的 props 只能是 value ,如果要传入多个变量,得写成 Object 形式。
Button.js
jsx import React from "react" ;
import { ThemeContextConsumer } from "./themeContext" ;
function Button ( props ) {
return (
< ThemeContextConsumer >
{( context ) => (
< button
onClick = {context.toggleTheme}
className = { `${ context . theme }-theme` }
>
Switch Theme
</ button >
)}
</ ThemeContextConsumer >
);
}
export default Button;
Hooks⭐️
React 内置的 Hooks 包括 useState
, useEffect
, useContext
, useRef
, useReducer
(常用)
useMemo
, useCallback
, useImperativeHandle
, useLayoutEffect
, useDebugValue
.
你也可以自定义 Hooks。
useRef
useRef
的主要的功能就是帮助我们获取到 DOM 元素或者组件实例,它还可以保存在组件生命周期内不会变化的值。
有时候希望点了按钮后,聚焦仍在输入框里,这时可以用 useRef
。
Usage: Manipulating the DOM
下例是一个 Todo list:
jsx const inputRef = useRef ( null );
function addTodo ( event ) {
event. preventDefault ();
setTodosList (( prevTodosList ) => [ ... prevTodosList, newTodoValue]);
setNewTodoValue ( "" );
inputRef.current. focus ();
}
return (
< div >
< form >
< input
ref = {inputRef}
type = "text"
name = "todo"
value = {newTodoValue}
onChange = {handleChange}
/>
< button onClick = {addTodo}>Add todo item</ button >
</ form >
</ div >
);
ref={inputRef}
意思是 inputRef 变量指向该 input 输入框
inputRef.current.focus()
意思是聚焦于该变量指向的输入框(直接敲键盘可以输入,无需鼠标点击)
Usage: Referencing a value
jsx const intervalRef = useRef ( null );
function handleStart () {
//...
intervalRef.current = setInterval (() => {
//...
}, 10 );
}
function handleStop () {
clearInterval (intervalRef.current);
}
Since the interval ID is not used for rendering, it’s appropriate to keep it in a ref, and manually update it.
useReducer
It's very similar to useState
, but it lets you move the state update logic from event handlers into a single function outside of your component.
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 the useReducer()
hook.
Syntax : const [state, dispatch] = useReducer(reducer, initialState, init?);
initialState
In the case of a counter state, the initial value could be like:
jsx const initialState = {
counter: 0 ,
};
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 .
jsx function reducer ( state , action ) {
let newState;
switch (action.type) {
case "increase" :
newState = { counter: state.counter + 1 };
break ;
case "descrease" :
newState = { counter: state.counter - 1 };
break ;
default :
throw new Error ();
}
return newState;
}
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.
jsx const action = {
type: "add" ,
user: {
name: "Harris" ,
sex: "male" ,
},
};
dispatch
The dispatch
is a special function that dispatches an action object.
Syntax : dispatch(actionObject)
useContext
通过 useContext
拿到上下文里的数据。之前介绍过,这里直接上例子,看与之前写的有什么不同(Advanced React/Context/Move…),使用useContext
代码会更简洁。
ThemeContext.js
jsx const ThemeContext = React. createContext ();
const ThemeContextProvider = ( props ) => {
const [ theme , setTheme ] = useState ( "light" );
const toggleTheme = () => {
setTheme (( prevTheme ) => (prevTheme === "light" ? "dark" : "light" ));
};
return (
< ThemeContext.Provider value = {{ theme, toggleTheme }}>
{props.children}
</ ThemeContext.Provider >
);
};
export { ThemeContextProvider, ThemeContext };
index.js(保持不变)
jsx import ReactDOM from "react-dom" ;
import App from "./App" ;
import { ThemeContextProvider } from "./themeContext" ;
ReactDOM. render (
< ThemeContextProvider >
< App />
</ ThemeContextProvider >,
document. getElementById ( "root" )
);
Button.js(重点)
jsx import { ThemeContext } from "./themeContext" ;
function Button ( props ) {
//重点
const context = useContext (ThemeContext);
return (
< button onClick = {context.toggleTheme} className = { `${ context . theme }-theme` }>
Switch Theme
</ button >
);
}
export default Button;
It lets you cache a function definition between re-renders. It can prevent creating new functions in each re-render and optimise the performance.
jsx export default function ProductPage ({ productId , referrer , theme }) {
const handleSubmit = useCallback (( orderDetails ) => {
post ( '/product/' + productId + '/buy' , {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
< div className = {theme}>
< ShippingForm onSubmit = {handleSubmit} />
</ div >
);
In the above case, we don't want the change of theme
to cause the re-render of <ShippingForm>
. So we have to keep handleSubmit()
not affected by the re-render due to the change of theme
.
It lets you cache the result of a calculation between re-renders. It can prevent unnecessary recalculations in each re-render and optimise the performance.
jsx function TodoList ({ todos , tab , theme }) {
const visibleTodos = useMemo (() => filterTodos (todos, tab), [todos, tab]);
// ...
}
When the prop theme
has changed, it will trigger the TodoList component re-render, and expensive filterTodos
function will re-calculate. So we should use useMemo
to let filterTodos
only render when the dependencies todos
or tab
change.
Custom Hooks
自定义 Hook 是一种让你能够将组件里可复用的逻辑提取到函数中的方式。自定义 Hook 可以让你在不增加组件的情况下,复用组件之间的状态逻辑 ,从而使代码更加简洁、清晰。
与 Component 直观区别是,Hook 是复用 JS 逻辑,Component 是复用 HTML 代码。
useCounter.js
js import { useState } from "react" ;
function useCounter () {
const [ count , setCount ] = useState ( 0 );
function increment () {
setCount (( prevCount ) => prevCount + 1 );
}
return [count, increment];
}
export default useCounter;
Note:return [count, increment]
部分也可以写成 return {count, increment}
,这样的话,在接收时就必须使用该变量名了。
App.js
jsx import React, { useState } from "react" ;
import useCounter from "./useCounter" ;
function App () {
const [ number , add ] = useCounter ();
return (
< div >
< h1 >The count is {number}</ h1 >
< button onClick = {add}>Add 1</ button >
</ div >
);
}
export default App;
React 路由 可以有效管理多个视图(组件)实现 SPA(single page application,单页应用)
Installation 安装
npm add react-router-dom
下面笔记是根据 V6 版本写的
BrowserRouter 与 HashRouter
在最外层包裹整个应用,它相当于 context
<BrowserRouter>
:
jsx import React from "react" ;
import ReactDOM from "react-dom" ;
import { BrowserRouter as Router } from "react-router-dom" ;
ReactDOM. render (
< Router >
< App />
</ Router >,
document. getElementById ( "root" )
);
BrowserRouter 模式,访问地址:https://<url>/xxx
<HashRouter>
:
jsx import React from "react" ;
import ReactDOM from "react-dom" ;
import { HashRouter as Router } from "react-router-dom" ;
ReactDOM. render (
< Router >
< App />
</ Router >,
document. getElementById ( "root" )
);
HashRouter 模式,访问地址:https://<url>/#/xxx
两者对比:
hash 模式兼容好,history 模式是 HTML5 提出的,兼容性差(移动端兼容性不错)
hash 模式下,访问不存在的页面,不需要单独配置 nginx。而 history 模式需要。
history 模式路由更好看;hash 模式带#号,不美观,而且处理描点链接不方便。
<Routes> & <Route>
<Routes>
它用来包裹 <Route>
<Route>
相当于 if 语句,配置路由规则和要展示的组件(路由出口),可以嵌套使用,需要通过<Outlet/>
组件来渲染子路由;
jsx import { Routes, Route } from "react-router-dom" ;
< Routes >
{ /* 1. path 定义路径,element 定义对应渲染组件 */ }
< Route path = "/" element = {< Login />} />
< Route path = "/layout" element = {< WebLayout />}>
{ /* 2. 嵌套路由需要<Outlet />组件配合使用,该路由访问路径http://localhost:8080/layout/main */ }
< Route path = "main" element = {< Home />} />
</ Route >
{ /* 3. Route 也可以不写element属性,展示嵌套路由,对应路由是:/tab/list */ }
< Route path = "/tab" >
< Route path = "list" element = {< List />} />
</ Route >
{ /* 4. Route 定义replace属性为true的时候,不会产生历史历史记录。默认是false */ }
< Route path = "/list" element = {< List >} replace={ true } />
{ /* 5. Route 定义Index属性,不需要定义path属性,在子路由里显示该路由页面。 */ }
< Route path = "/main" element = {< Main />}>
< Route index element = {< Test1 />} />
{ /* 6. 定义动态的路由,访问地址:http://localhost:8080/main/1 */ }
< Route path = ":id" element = {< Test2 />} />
{ /* 访问路径:http://localhost:8080/main/list/1 */ }
< Route path = "list/:id" element = {< Test2 />} />
</ Route >
</ Routes >
<Link> 与 <NavLink>
<Link>
类似于 a 标签的,是基于 a 标签封装的组件。 最终<Link>
组件会转为 a 标签。通常应用就是在不同页面之间跳转。
有三种传参方式:
params
searchParams
state
必须被<BrowserRouter />
或 <HashRouter />
包裹
jsx import { Link } from "react-router-dom" ;
{ /* to的路由是对象,属性pathName。定义三种传参方式如下: */ }
< Link to = {{ pathName: "/" }}></ Link >
{ /* 1. params传参 👉 /detail/:id */ }
< Link to = "/detail/1" >< Link >
< Link to = { `/detail/${ id }` }>< Link >
< Link to = "/detail/:id" >< Link >
{ /* 2. searchParams传参 👉 /detail?id=1 */ }
< Link to = "/detail?id=1" ></ Link >
< Link to =`/detail?id=${id}`></Link>
{ /* 3. state 传参 */ }
<Link to = "/" state = {{ id: 1 }}>
{ /* replace 为true的时候,不产生历史记录, 默认replace={false} */ }
< Link to = "/detail" replace = { true }>
<NavLink>
组件和<Link>
组件使用一致,但它相当于激活版的 <Link>
组件,可以实现导航被选中的高亮效果。通常的使用场景是在同一个界面,tab 栏切换,导航高亮。
jsx import { NavLink } from "react-router-dom" ;
// NavLink 组件的class 会返回含有isActive的对象,为true的时间,改导航路由被激活。
< NavLink
to = "/go"
className = {({ isActive }) => {
return isActive ? "red" : "blue" ;
}}
>
{ " " }
go{ " " }
</ NavLink >;
State 参数
state 是可选参数,通常在不同页面之间传递数据时,可以将数据隐式存储在 state 中,然后在目标页面中访问它们。它好处是不会将数据暴露在 URL 中,也不会被出现在浏览器历史记录里,从而保持 URL 的简洁性、美观性和保护用户隐私。
<Navigate>
<Navigate>
组件被渲染,路径被修改,切换视图。它和<Link>
不同之处是,前者是渲染后跳转,后者需要点击事件发生才会跳转。
jsx import { Navigate } from "react-router-dom" ;
< Navigate to = "/" />
< Navigate to = "/" replace = { true } />
<Outlet />
当 route 发生嵌套的时候,必须使用<Outlet />
组件来渲染子路由 对应的视图,相当于子路由渲染组件的占位符。
jsx import { Routes, Route, Outlet } from "react-router-dom" ;
function Module () {
return (
< div >
< h2 >我是Modules组件</ h2 >
// 如果不写 < Outlet /> 组件,< Item /> 视图是渲染不出来的。
< Outlet />
</ div >
)
}
function Item () {
return (
< div >
< h2 >我是Item组件</ h2 >
</ div >
)
}
function App () {
return (
< Routes >
< Route to = "/tab" element = { < Module /> }>
< Route to = "item" element = { < Item /> }>
</ Route >
</ Routes >
)
}
useRoutes() 🔥
接收数组生成路由表,动态创建 Routes 和 Route。类似 vue-router, 通过 js 配置路由。
Router.jsx
jsx // 这里也可以定义纯js,定义router.js配置路由,类似vue中那样配置路由。
import React from "React"
import { useRoutes, Navigate } from "react-router-dom"
// 利用React.lazy 懒加载组件
const List = React. lazy (() => import ( "../views/list.jsx" ))
// 懒加载组件
function lazyComponent ( path ) {
const Comp = React. lazy (() => import ( `../views/${ path }` ));
return (
// fallcack 可自定义全局<Loading />组件,在切换页面或模块显示loading过程,提高用户体验。
< React.Suspense fallback = { < Loading title = "加载中..." /> }>
< Comp />
</ React.Suspense >
)
}
const routes = [
{
path: "/" ,
element: < Home />
},
{
path: "/list" ,
element: < List />
},
{
path: "/detail" ,
element: lazyComponent ( "detail.jsx" )
},
{
path: "/login" ,
element: < Navigate to = "/login" />
},
{
path: "/parent" ,
element: < Parent />,
children: [
{
index: true ,
element: < Children />
},
{
path: "children1" ,
element: < Children1 />
children: [
{
path: "" ,
element: < Navigate to = "/parent/children1/abc" />,
},
]
}
]
}
]
export const RouteList = () => {
const router = useRoutes (routes);
return <> { router } </>
}
App.js
jsx import { RouteList } from "./Router.jsx" ;
function App () {
return < RouteList />;
}
index.js
jsx import React from "react" ;
import ReactDOM from "react-dom" ;
import { BrowserRouter as Router } from "react-router-dom" ;
ReactDOM. render (
< Router >
< App />
</ Router >,
document. getElementById ( "root" )
);
useNavigate()
它和 history.push()
类似,语法会更简洁些,React Router v6 版本建议使用它进行路由跳转。
通过 useNavigate()
返回的参数 navigate
是一个函数。
jsx import { useNavigate } from "react-router-dom" ;
export const Demo = () => {
const navigate = useNavigate ();
const handle = () => {
navigate ( "/login" , {
replace: true ,
state: {
name: "error" ,
},
});
// 上述是state传参,params 和 searchParmas 传参的话, 需要手动拼接
// - params
navigate ( `/detail/${ id }` );
// - searchParmas
navigate ( `/detail?name=${ name }&age=${ age }` );
};
return (
< div >
// 类似history.go(-1)
< button onClick = {() => navigate ( - 1 )}>后退</ button >
// 类似history.go(1)
< button onClick = {() => navigate ( 1 )}>前进</ button >
< button onClick = {handle}>按钮</ button >
</ div >
);
};
useParams()
有时需要获取路由传来的值
App.js(定义路由)
jsx < Routes >
< Route path = "/services" element = {< ServicesList />} />
< Route path = "/services/:serviceId" element = {< ServiceDetail />} />
</ Routes >
ServicesList.js(链接传值)
jsx function ServicesList () {
const services = servicesData. map (( service ) => (
< h3 key = {service._id}>
< Link to = { `/services/${ service . _id }` }>{service.name}</ Link >
</ h3 >
));
return (
< div >
< h1 >Services List Page</ h1 >
{services}
</ div >
);
}
ServiceDetail.js(用 useParams
获取链接值,serviceId 这个变量名一定要与 router 里的名称对应)
jsx import {useParams} from "react-router-dom"
function ServiceDetail ( props ) {
const { serviceId } = useParams ()
const thisService = servicesData. find ( service => service._id === serviceId)
return (
< h1 >Service Detail Page</ h1 >
< div >{thisService}</ div >
)
}
useSearchParams()
读取和修改当前位置的 URL 中的 query 对象
返回包含俩值的数组,内容分别为:当前的 search 参数、更新 search 的函数
App.js
jsx import { useSearchParams } from "react-router-dom" ;
< Routes >
< Route to = "/list?name=harris&age=18" element = {< Detail />} />
</ Routes >;
detail.jsx(访问路由https://<url>/list/1
)
jsx import { useSearchParams } from "react-router-dom" ;
export const Detail = () => {
// 获取URL中携带过来的searchParams参数
const [ search , setSearch ] = useSearchParams ();
const name = search. get ( "name" ); // harris
const age = search. get ( "age" ); // 18
// 点击,url变成http://localhost:3000/detail?name=zhang&age=19
return (
< div onClick = {() => setSearch ( "name=Wendy&age=17" )}>设置urlParams</ div >
);
};
用了 set 方法后也只是生成了新的 url,要访问该它还得写一个页面跳转方法
useRouteMatch()
有时需要更改路由路径名称,那其全部子路由需要手动更改了,很麻烦,这时需要用 useRouteMatch
,有了 useRoutes()
就不需要用这个了。
jsx import { Link, Routes, Route, useRouteMatch } from "react-router-dom" ;
function Profile () {
const { path , url } = useRouteMatch ();
return (
< div >
< h1 >Profile Page</ h1 >
< ul >
< li >
< Link to = { `${ url }/info` }>Profile Info</ Link >
</ li >
< li >
< Link to = { `${ url }/settings` }>Profile Settings</ Link >
</ li >
</ ul >
< Routes >
< Route path = { `${ path }/info` }>
< Info />
</ Route >
< Route path = { `${ path }/settings` }>
< Settings />
</ Route >
</ Routes >
</ div >
);
}
Note : 官方文档建议 url
写在 <Link/>
里,path
写在 <Route/>
里。
useHistory()
有时要用 JS 代码实现页面跳转。但建议用 useNavigate()
。
使用 history.push("/pathname")
可跳转到路径为 /pathname
页面
jsx import { useHistory } from "react-router-dom" ;
function ServiceDetail () {
const history = useHistory ();
function handleClick () {
history. push ( "/services" );
}
return (
< div >
< button onClick = {handleClick}>Go back to all services</ button >
</ div >
);
}
例子里的 history
是 React 路由提供的,用于获取浏览器历史记录的相关信息,还有很多用法:
history.goBack()
:回退到上个页面
history.goForward()
:前进到下个页面
history.go(int)
:前进或回退到某个页面,参数 int 表示前进或后退页面数量(例如:-1 表示回退到上一页)
history.replace(/pathname)
: 替换当前页面历史记录,这样可以禁止用户回退到上一页面。
useLocation()
有时需要获取当前 URL 的位置信息,包括路径名 pathname、搜索参数 search、状态值 state 等等
jsx import { useLocation } from "react-router-dom" ;
function MyComponent () {
const location = useLocation ();
console. log (location); // 打印内容如下:
/*
{
hash: "",
key: "h8s9s",
pathname: "/list",
search: "?name=harris&age=18",
state: {a: 1, b: 2}
}
*/
}
如果在搜索框里输入 /list?name=harris&age=18
useOutlet()
在嵌套路由中使用,父组件使用 <Outlet />
, 当子组件渲染的时候,会展示嵌套路由的对象。如果嵌套路由没有挂载,返回是 null。
useOutlet()
相较于<Outlet />
的好处是:
可获取子路由相关信息
可定制化子路由的渲染逻辑
jsx import { useOutlet } from "react-router-dom" ;
function Dashboard () {
const outlet = useOutlet ();
console. log (outlet.pathname); // "/dashboard"
console. log (outlet.parentParams); // 路由参数
console. log (outlet.parentPath); // "/dashboard"
console. log (outlet.route); // 当前路由对象
console. log (outlet.router); // 当前router对象
console. log (outlet.match); // 当前match对象
return (
< div >
{ /* 渲染Outlet */ }
{outlet}
</ div >
);
}
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
jsx function Parent () {
const [ count , setCount ] = React. useState ( 0 );
return < Outlet context = {[count, setCount]} />;
}
Child.jsx
jsx import { useOutletContext } from "react-router-dom" ;
function Child () {
const [ count , setCount ] = useOutletContext ();
const increment = () => setCount (( c ) => c + 1 );
return < button onClick = {increment}>{count}</ button >;
}
Redirect 重定向
有时候需要页面重定向,比如检测是否登陆,若无就重定向到登陆界面。
语法:<Redirect to="/pathname" />
这是 React Router v4 版本,v6 版本推荐用<Navigate />
,关键还能传递参数和状态等信息。
jsx import { Navigate } from 'react-router-dom' ;
// 父亲路由
{
path : '/auth' ,
element : (
< Outlet />
),
children : [
{
path: '' ,
element : (
< Navigate to = "/auth/login" />
)
},
{
path: '/login' ,
element : (
< Login />
)
}
]
}
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
jsx import styled from "styled-components" ;
const Title = styled. h1 `
color: #666;
` ;
const Content = () => {
return (
<>
< Title >💅 Section</ Title >
</>
);
};
Styling through Props
jsx import styled from "styled-components" ;
const Title = styled. h1 `
color: ${ ({ color }) => color };
` ;
const Content = ({ color }) => {
return (
<>
< Title color = {color}>💅 Section</ Title >
</>
);
};
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
.
json {
"posts" : [
{ "id" : 1 , "title" : "React" , "content" : "It's a good tool." },
{
"id" : 2 ,
"title" : "Looking for a job" ,
"content" : "It's hard to find a job."
}
],
"user" : [{ "id" : 1 , "username" : "Harris Wong" , "pwd" : "123456" }]
}
We need to make sure that we provide an id
value so that we can query the database later.
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:
json {
"id" : 1 ,
"title" : "React" ,
"content" : "It's a good tool."
}
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
json "scripts" :{
"start" : "npx concurrently \" npm run start::server \" \" npm run start:client \" " ,
"start:server" : "npx json-server --watch server/db.json --port 4000" ,
"start:client" : "react-scripts start" ,
}
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/
to http://localhost:4000/
.
Now, let's configure the proxy:
npm install --save http-proxy-middleware
Then, we create a new src/setupProxy.js
file:
js const 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 组件
Project
Folder Structure
Recommended Video: Junior vs Senior React Folder Structure
通常,src 目录结构如下:
plaintext src/ 项目源码,写项目功能代码
assets/ 资源(图片、字体等)
components/ 公共组件
pages/ 页面
containers/ 容器
utils/ 工具
App.js 根组件(配置路由信息)
index.css 全局样式
index.js 项目入口文件(渲染根组件、导入组件库)
components/
里放一些公用 UI 组件,包括导航栏和页尾等
containers/
里放需要与数据和状态有关的 UI 组件,负责从数据层获取数据,处理用户交互,然后传递数据到 UI 组件进行呈现。
Project Setup 项目安装 1
这里使用的是 Vite,它是一种新型前端构建工具,能够显著提升前端开发体验
bash npm create vite@latest
y
react
javascript
或者 👇
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
文件,添加以下代码到文件中:
json "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:
bash npm run build
npm run deploy
部署到 Vercel
首先,你需要创建一个 Vercel 账户,并登录到 Vercel 控制台。
在控制台中,点击“New Project”,并选择“Import Git Repository”选项,将你的 GitHub repository 导入到 Vercel 中。
确保你的项目中有一个package.json
文件,并在其中设置start
脚本。例如:
json "scripts" : {
"start" : "react-scripts start"
}
点击“Deploy”,等待 Vercel 构建和部署你的应用程序。
一旦部署成功,你的应用程序将会自动在 Vercel 上运行。
Utils
安装
npm install antd --save
自定义主题 & 深浅切换
自定义主题在官网可以自己定制主色、尺寸等等,然后复制代码到 token 这里。
js import { theme, ConfigProvider, Button } from "antd" ;
import { useState } from "react" ;
import "./App.css" ;
function App () {
const [ darkMode , setDarkMode ] = useState ( false );
return (
< ConfigProvider
theme = {{
token: {
colorPrimary: "#ff8a00" ,
},
algorithm: darkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
}}
>
< div className = "App" >
< Button type = "primary" onClick = {() => setDarkMode ( ! darkMode)}>
Button
</ Button >
</ div >
</ ConfigProvider >
);
}
export default App;
安装
npm install @mui/material @emotion/react @emotion/styled
自定义主题 & 深浅切换
可以用 mui-theme-creator 帮助设计和自定义 MUI 组件库主题,还可以用 Material palette generator 为您输入的任何颜色生成调色板。
jsx import { ThemeProvider, createTheme } from "@mui/material" ;
function App () {
const theme = createTheme ({
palette: {
mode: "light" ,
primary: {
main: "#3d993d" ,
},
},
});
return (
<>
< ThemeProvider theme = {theme}>
< Router />
</ ThemeProvider >
</>
);
}
您可以使用主题中的键来自定义组件的样式、默认 props 等。这有助于在整个应用程序中实现样式的一致性。
这是一个简单的 Apache echarts 的 React 封装。
npm install --save echarts-for-react
Icons
npm install react-icons
Admin Dashboard
Recommended Video : React Admin Dashboard
Official Docs :
Recommended Video : React Hook Form Tutorial
References
Docs :
Videos :