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