React 快速上手
文章目录
- 1、新建项目
- 2、需注意的语法
- 3、state 与 setState 更新数据重新渲染
- 4、引入 CSS 样式
- 5、函数式组件
- 6、Hooks
- useState 修改数据
- useEffect 生命周期
- useContext 父子组件传数据
- 父传子
- 子传父
- createContext 跨级传值
- useContext
- useRef 获取表单不受控组件值
- memo 暂缓子组件更新
- useCallback 与 useMemo
- 路由
- 简单路由
- Link & useNavigate & useLocation
- useParams & useSearchParams 获取 url 参数
- 携带大量参数的事件跳转
- 404 页面
笔记基于 这个视频
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