创建新项目
npx create-react-app app-name
删除 src/
下所有文件。
创建必须的入口文件 index.js
import ReactDOM from 'react-dom'
import App from './App'
// ReactDOM.render(组件名称, 要注入的元素)
ReactDOM.render(
,
document.getElementById('root')
)
React 18 不再支持 ReactDOM.render
,这里改用 createRoot
:
import ReactDOM from 'react-dom/client'
import App from './App2'
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( );
这里类组件在 App.jsx
文件中定义:
import React from 'react'
const msg = "hello"
// 类组件
class App extends React.Component {
render(){
return (
{msg}
)
}
}
export default App
jsx
或者 js
。一般 react 文件用 jsx
。()
代表其中想要写html{}
代表其中相要写jsexport default
也可以写到class前面export default class App extends React.Component { }
建一个组件 App1.jsx
并渲染:
import React, { Component } from 'react'
const msg = "hello";
let flag = false;
let arr = ["1", "2", "3"]
export default class App1 extends Component {
render() {
return (
{msg}
Box
{/* 这里用了三元运算符判断颜色 */}
{ backgroundColor: flag ? "pink" : "skyblue" }}>Content
{
// React 中列表循环仅有 map 可以使用,forEach 没有返回值。
arr.map((item, index) => {
return - {item}
})
}
)
}
}
lable
标签的 for
要写成 htmlfor
。class
要写成 className
。这里直接 .box
回车就会自动生成 div className="box">
style={{backgroundColor: "skyblue"}}
其实可以写成 style={bgc}
,bgc 定义为 const bgc = {backgroundColor: "skyblue"}
可以直接用空标签 <>>
替代。map
可以使用,forEach
没有返回值。li
需要填写 key
。li
中可写为:arr.map((item, index) => - {item}
)
效果如下:
在 App2.jsx
中实现累加:
import React, { Component } from 'react'
export default class App2 extends Component {
state = {
num: 1
}
render() {
return (
number: {this.state.num}
)
}
}
上面的代码中 state
是简写,完整写法应该放在类的构造器中:
export default class App2 extends Component {
constructor(props) {
super(props)
this.state = {
num: 1
}
}
render() {
return (
number: {this.state.num}
)
}
}
在点 button 时打印日志:
如果 setState
中内容较多,可单独抽出来一个函数,这是需要注意 this
如何传入:
render() {
return (
number: {this.state.num}
{/* 使用 bind */}
{/* 使用箭头函数 */}
)
}
addNum(){
this.setState({num: this.state.num + 1})
}
在函数中传递参数:
render() {
return (
number: {this.state.num}
)
}
addNum(num) {
this.setState({ num: num})
}
第 2 部分使用内嵌 style
实现样式,复杂样式可以在对应的 .css
文件中实现。
首先要定义组件的 className
,导入 CSS 文件,在 CSS 文件中用类名指定样式。
// App2.jsx
import React, { Component } from 'react'
import "./App2.css"
export default class App2 extends Component {
render() {
return (
hello
)
}
}
/* App2.css */
.box{
width: 200px;
height: 100px;
background: skyblue;
}
效果如下:
前面都是用的类组件,接下来在 App3.jsx
使用函数式组件。
export default function App3() {
return (
App3
)
}
或者使用 ES6 中的箭头函数:
const App3 = () => {
return (
App3
)
}
export default App3;
Hook:钩子,生命周期钩子,或者说生命周期函数。
Hook 只能用在组件函数的最顶层。
import { useState } from 'react'
export default function App3() {
const xxx = useState("hello")
console.log(xxx)
}
调用导入的 useState
,打印出来可以在浏览器 Console 中看到返回的是个数组,第一个元素为传入参数,第二个元素为修改参数的函数。
所以可以把这两个对象接下来,在之后调用修改函数即可实现和 state/setState
类似的功能。
import { useState } from 'react'
export default function App3() {
const [msg, setMsg] = useState("hello")
return(
<>
{msg}
>
)
}
在 vue 中主要有三种生命周期:
React 函数式组件没有生命周期,需要使用 useEffect
实现类似功能。
import { useState, useEffect } from 'react'
export default function App3() {
const [num1, setNum1] = useState(1);
const [num2, setNum2] = useState(1);
useEffect(() => {
console.log("num1 update")
return () => {
console.log("destroy")
}
}, [num1])
return (
<>
{num1}
{num2}
>
)
}
useEffect
的回调函数就可以实现**挂载完成(mounted)**功能,一般用来处理 ajax 数据请求。单独使用时不管是第一次加载还是之后数据更新重新渲染都会执行一次。
如果要用来检测某个数据更新(update),就在第二个数组参数中添加这个数据。数组为空表示对所有数据更新都不监听。以上代码表示监听 num1
更新。
要实现销毁阶段(beforeDestroy),就在回调函数中 return 一个箭头函数,在箭头函数中执行销毁组件之前的操作。
父组件返回子组件标签即显示子组件,传值直接在标签中加参数即可,子组件函数接收父组件的传入。
import React from 'react'
//子组件
function Child(props) {
return Child - {props.num}
}
//父组件
function Father(props) {
return
}
// 顶级组件
export default function App4() {
return
}
子传父实际上是使用 useState
,从父组件传了一个函数给子组件调用,实际上还是父组件在操作。
import React, { useState } from 'react'
//子组件
function Child(props) {
return (<>
Child - {props.num}
>)
}
//父组件
function Father(props) {
return
}
// 顶级组件
export default function App4() {
const [num, setNum] = useState(123)
// 提供给子组件用来修改 num 的函数
const changeNumFn = (num) => setNum(num)
return
}
这里的 changeNumFn
实际没有必要,直接把 setNum
传下去就行。这里这样做能拿到子组件传来修改后的 num
值,也可以进行其他处理。
如果是复杂的多层组件,很难保证中间层没有问题,可以用 createContext
跨层传数据。Provider 用 value
传值,Consumer 接收并取出传值。
import React, { useState, createContext } from 'react'
// 创建上下文(Provider, Consumer)
const NumContext = createContext()
//子组件
function Child() {
return (
{ // 解构 value
({ num, setNum }) => (<>
Child - {num}
>)
}
)
}
// 父组件直接用简写
const Father = () =>
// 顶级组件
export default function App4() {
const [num, setNum] = useState(123)
return (
{ num, setNum }}>
)
}
上面 Consumer 使用过于复杂,js 嵌套 html 容易写错,所以使用 useContext
接收数据。
import React, { useState, createContext, useContext } from 'react'
// 创建上下文(Provider, Consumer)
const NumContext = createContext()
//子组件
function Child() {
// useContext 接收数据,注意用{},非数组[]
const { num, setNum } = useContext(NumContext)
return (<>
Child - {num}
>)
}
// 父组件
const Father = () =>
// 顶级组件
export default function App4() {
const [num, setNum] = useState(123)
return (
{ num, setNum }}>
)
}
受控组件:代表表单元素的值受state/useState
控制,获取其值时直接通过state获取即可。
以下代码实现 iuput 组件获取输入的值 value
,值随输入的改变而改变 onChange
,点击按钮时打印输入的值。
import React, { useState } from 'react'
export default function App5() {
const [val, setVal] = useState("abc")
return (
setVal(e.target.value)} />
)
}
不受控组件:代表表单元素的值不受state控制,那么获取表单元素的值便只能通过 ref/useRef
获取。
import React, { useRef } from 'react'
export default function App5() {
const element = useRef(null)
return (
)
}
当父组件更新时,子组件会被迫更新,这就导致性能损耗,此时,我们需要借助 memo
来缓存Sub组件,避免它被强制更新:
import React, { useState, memo } from 'react'
// 子组件
const Sub = memo(() => {
console.log('子组件被更新了')
return 子组件
})
// 父组件
export default function App() {
const [msg, setMsg] = useState("你好世界");
return (<>
内容为:{msg}
>)
}
memo
只有在回调函数是静态的,不产生交互修改时才有效。如果想在子组件中调用父组件的修改函数,那子组件依然会刷新:
import React, { useState, memo } from 'react'
// 子组件
const Child = memo((props) => {
console.log('子组件被更新了')
return
})
// 父组件
export default function App() {
const [num, setNum] = useState(1);
const doSth = () => setNum(num + 1)
return (<>
Number: {num}
>)
}
此时就可以将 doSth
使用 useCallback
嵌套,第二个数组参数为空表示不检测任何元素更新,若里面填 num
,则 num
更新时就会触发子组件更新。
import React, { useState, memo, useCallback } from 'react'
// 子组件
const Child = memo((props) => {
console.log('子组件被更新了')
return
})
// 父组件
export default function App() {
const [num, setNum] = useState(1);
const doSth = useCallback(() => setNum((num) => num + 1), [])
return (<>
Number: {num}
>)
}
注意这里使用 setNum((num) => num + 1)
,可以不断用新值覆盖旧值,如果只有 setNum(num + 1)
那就只覆盖一次初始值。
useMemo
使用和 useCallback
相似,只有 doSth
需要再 return
一个回调函数。
const doSth = useMemo(() => {
return () => setNum((num) => num + 1)
}, [])
安装 react-router-dom
npm install react-router-dom@pre localforage match-sorter sort-by
在 src 下新建 pages 和 router 文件夹,pages 中创建三个 rfc 默认页面,router 中建一个路由组件。
在 src/router/index.jsx
中定义一个路由:
import App from "../App";
import Home from "../pages/Home";
import List from "../pages/List";
import Detail from "../pages/Detail";
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const BaseRouter = () => {
return (
} >
} >
} >
} >
)
}
export default BaseRouter;
在 src/index.js
中引入路由组件(之前直接引入的 App 组件)
import ReactDOM from 'react-dom/client'
import Router from './router/index.jsx'
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( );
此时在浏览器输入路由还无法跳转,在 App.jsx
中加 Outlet 标签可以显示子路由对应组件。
import React from 'react'
import { Outlet } from 'react-router-dom'
export default function App() {
return (<>
App
>)
}
效果如下:
在 App.jsx
中加入 Link
标签实现点击跳转页面。
使用 useLocation
可以获取当前页面路径。
要使用事件跳转,就需要 useNavigate
了,如下点击按钮跳转 detail。
import React from 'react'
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom'
export default function App() {
const location = useLocation()
console.log(location.pathname)
const navigate = useNavigate()
const goDetail = () => {
navigate('/detail')
}
return (<>
- Home
- List
- Detail
>)
}
/* 这样也行,不过刷新慢
- Home
- List
- Detail
*/
在 App.jsx
中修改 list 跳转,加入子路由 /123
作为参数。
- List
router/index.jsx
中也要修改,表示后面可带 id 参数:
} >
那如何在 List.jsx
中获取这个参数呢?
使用 useParams
:
import React from 'react'
import { useParams } from 'react-router-dom'
export default function List() {
const pageParam = useParams()
console.log(pageParam.id)
return (
List
)
}
在 App.jsx
中修改 home 跳转,用 ?
的形式携带参数:
- Home
在 Home.jsx
中使用 useSearchParams
获取这个参数。getAll('id')
获得一个 id 参数的列表,如果用 get('id')
只得到第一个 id 参数。
import React from 'react'
import { useSearchParams } from 'react-router-dom'
export default function Home() {
const parm = useSearchParams()
console.log(parm[0].getAll('id'))
return (
Home
)
}
如果需要携带的参数很多,需要使用事件跳转,在 navigate
的第二个参数添加携带的对象。在 App.jsx
中修改事件函数,传入 state
,里面有 username
参数:
const goDetail = () => {
navigate('/detail', {
state: {
username: 'wu',
}
})
}
在 Detail.jsx
中使用 useLocation
即可接收到传入的参数:
import React from 'react'
import { useLocation } from 'react-router-dom'
export default function Detail() {
console.log(useLocation())
return (
Detail
)
}
这里注意两个不同的跳转按钮,一个带参数一个不带时,如果需要接收参数使用,一定要判断参数为空的情况。
在 /pages
新建 Error.jsx
。
router/index.jsx
导入页面,新建一个和根页面平级的页面导入 Error,路径设为通配符:
} >
} >
} >
} >
} >
这里需要注意的是导入图片需要使用 import