前端 React React18基本使用 cgd 2022-02-11 2022-02-11 1. React-Vite 搭建 1 npm create vite@latest my-react-app -- --template react
2. 绑定 class 和 style 2.1 动态绑定 class 2.1.1 写法一 1 2 3 4 5 6 7 8 9 import React , { memo, useState } from "react" ;const App = memo (() => { const [isActive, setIsActive] = useState (true ); return <div className ={ `title ${isActive ? "active " : ""} `}> App</div > ; }); export default App ;
2.1.2 写法二 1 2 3 4 5 6 7 8 9 10 11 12 13 import React , { memo, useState } from "react" ;const App = memo (() => { const [isActive, setIsActive] = useState (true ); const classList = ["total" ]; if (isActive) { classList.push ("active" ); } return <div className ={classList.join( " ")}> App</div > ; }); export default App ;
2.1.3 写法三使用 classnames 库 安装 classnames
在 jsx 中使用
1 2 3 4 5 6 7 8 9 10 import React , { memo, useState } from "react" ;import classNames from "classnames" ;const App = memo (() => { const [isActive, setIsActive] = useState (true ); return <div className ={classNames( "total ", { active: isActive })}> App</div > ; }); export default App ;
2.2 绑定 style 1 2 3 4 5 6 7 import React , { memo, useState } from "react" ;const App = memo (() => { return <div style ={{ color: "red ", fontSize: "28px " }}> App</div > ; }); export default App ;
3. 条件渲染 3.1 写法一 使用三元运算符 1 2 3 4 5 6 7 8 9 10 11 import React , { memo, useState } from "react" ;const App = memo (() => { const [isShow, setIsShow] = useState (false ); return ( <div className ="app" > {isShow ? <button > show</button > : <h2 > show</h2 > }</div > ); }); export default App ;
3.2 写法二 使用&&运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import React , { memo, useState } from "react" ;const App = memo (() => { const [obj, setObj] = useState (null ); setTimeout (() => { setObj ({ name : "aaa" , }); }, 100 ); return <div className ="app" > {obj && obj.name} </div > ; }); export default App ;
3.2 写法三 使用?.可选链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import React , { memo, useState } from "react" ;const App = memo (() => { const [obj, setObj] = useState (null ); setTimeout (() => { setObj ({ name : "aaa" , }); }, 100 ); return <div className ="app" > {obj?.name} </div > ; }); export default App ;
4. 受控组件和非受控组件 4.1 受控组件 当一个表单绑定了 value 属性并且值来源于 state,并且只能通过使用 setState 来更新,使 React 的 state 成为“唯一数据源”,被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React , { memo, useState } from "react" ;const App = memo (() => { const [username, setUsername] = useState ("aaa" ); const inputChange = (e ) => { setUsername (e.target .value ); }; return ( <div className ="app" > <input type ="text" onChange ={inputChange} value ={username} /> </div > ); }); export default App ;
4.1.2 checkbox 标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import React , { memo, useState } from "react" ;const App = memo (() => { const [isAgree, setIsAgree] = useState (true ); const [hobbies, setHobbies] = useState ([ { value : "sing" , text : "唱" , checked : true }, { value : "dance" , text : "跳" , checked : true }, { value : "rap" , text : "rap" , checked : true }, ]); const agreeChange = (e ) => { setIsAgree (e.target .checked ); }; const hobbyChange = (e, index ) => { const newHobbies = [...hobbies]; newHobbies[index].checked = e.target .checked ; setHobbies (newHobbies); }; return ( <div className ="app" > <form action ="" > {/* 单选框 */} <label htmlFor ="agree" > <input type ="checkbox" id ="agree" checked ={isAgree} onChange ={agreeChange} /> {" "} 同意协议 </label > {/* 多选框 */} <div > 您的爱好: {hobbies.map((item, index) => { return ( <label htmlFor ={item.value} key ={item.value} > <input type ="checkbox" id ={item.value} checked ={item.checked} onChange ={(e) => hobbyChange(e, index)} /> {item.text} </label > ); })} </div > </form > </div > ); }); export default App ;
4.1.3 select 标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React , { memo, useState } from "react" ;const App = memo (() => { const [fruit, setFruit] = useState ("苹果" ); const fruitChange = (e ) => { setFruit (e.target .value ); }; return ( <div className ="app" > <form action ="" > <select value ={fruit} onChange ={fruitChange} > <option value ="apple" > 苹果</option > <option value ="orange" > 橘子</option > <option value ="banner" > 香蕉</option > </select > </form > </div > ); }); export default App ;
4.1.4 一个函数处理多个受控组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import React , { memo, useState } from "react" ;const App = memo (() => { const [username, setUsername] = useState ("aaa" ); const [password, setPassword] = useState ("123456" ); const inputChange = (e ) => { if (e.target .name === "username" ) { setUsername (e.target .value ); } else { setPassword (e.target .value ); } }; return ( <div className ="app" > <form action ="" > <label htmlFor ="username" > <input type ="text" name ="username" value ={username} onChange ={inputChange} /> </label > <label htmlFor ="password" > <input type ="password" name ="password" value ={password} onChange ={inputChange} /> </label > </form > </div > ); }); export default App ;
4.2 非受控组件 非受控组件的表单数据将交由 DOM 节点来处理,使用 ref 来获取输入的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React , { memo, useRef } from "react" ;const App = memo (() => { const inputRef = useRef (); const getInputData = ( ) => { console .log (inputRef.current .value ); }; return ( <div className ="app" > <input type ="text" ref ={inputRef} /> <button onClick ={getInputData} > 获取input数据</button > </div > ); }); export default App ;
5. css 的写法 5.1 写法一 内联样式 内联样式的优点
样式之间不会有冲突
可以动态绑定 state 中的数据
内联样式的缺点
没有样式提示
大量的样式代码混乱
伪类伪元素等无法编写
1 2 3 4 5 6 7 8 9 10 import React, { memo, useRef, useState } from 'react' const App = memo(() => { const [size, setSize] = useState(30) return ( <div className="app" style={{ color: 'red', fontSize: `${size}px` }}>App</div> ) }) export default App
5.2 写法二 引入 css 文件 缺点
编写的 css 是全局的 css,样式之间会相互影响
style.css
1 2 3 4 .app{ color: red; font-size: 20px; }
app.jsx
1 2 3 4 5 6 7 8 9 import React, { memo, useRef } from 'react' import './style.css' const App = memo(() => { return ( <div className="app">App</div> ) }) export default App
5.3 写法三 css 模块化 需要把.css/.less/.scss 文件编写成.module.css/.module.less/.module.scss
style.module.css
1 2 3 4 .app { color : red; font-size : 20px ; }
app.jsx
1 2 3 4 5 6 7 import React , { memo } from "react" ;import appStyle from "./style.module.css" ;const App = memo (() => { return <div className ={appStyle.app} > App</div > ; }); export default App ;
5.4 写法四 css in js
安装 styled-components
1 npm install styled-components
在 vscode 安装 vscode-styled-components 插件
style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import styled from 'styled-components' export const AppWrapper = styled.div` .header { font-size: 26px; font-weight: 700; } .content { display: flex; .left { color: red; } .right { color: blue; } } .footer{ color: purple; } `.app{ color: red; font-size: 20px; }
app.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import React, { memo } from 'react' import { AppWrapper } from './style' const App = memo(() => { return ( <AppWrapper> <div className="header">header</div> <div className="content"> <div className="left">left</div> <div className="right">right</div> </div> <div className="footer">footer</div> </AppWrapper> ) }) export default App
6. redux 6.1 在 node 环境中使用 redux 6.1.1 使用 redux 中的数据
安装
新建 store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const { createStore } = require ("redux" );const initialState = { name : "aaa" , }; function reducer ( ) { return initialState; } const store = createStore (reducer);module .exports = store;
在 main.js 中使用
1 2 3 const store = require ("./store/index" );console .log (store.getState ());
6.1.2 修改 redux 中的数据 store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const { createStore } = require ("redux" );const initialState = { name : "aaa" , age : 18 , }; function reducer (state = initialState, action ) { if (action.type === "change_name" ) { return { ...state, name : action.name }; } else if (action.type === "change_age" ) { return { ...state, age : action.age }; } return state; } const store = createStore (reducer);module .exports = store;
main.js
1 2 3 4 5 6 7 8 9 const store = require ("./store/index" );const nameAction = { type : "change_name" , name : "bbb" };const ageAction = { type : "change_age" , age : 20 };store.dispatch (nameAction); store.dispatch (ageAction);
6.1.3 订阅数据的变化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const store = require ("./store/index" );store.subscribe (() => { console .log (store.getState ()); }); const nameAction = { type : "change_name" , name : "bbb" };const ageAction = { type : "change_age" , age : 20 };store.dispatch (nameAction); store.dispatch (ageAction);
6.1.4 代码优化
新建 store/constants.js 定义常量
1 2 3 4 5 6 7 const CHANGE_NAME = 'change_name' const CHANGE_AGE = 'change_age' module.exports = { CHANGE_NAME, CHANGE_AGE }
新建 store/actionCreatore.js 动态生成 action
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { CHANGE_AGE, CHANGE_NAME } from './constants' const changeNameAction = (name) => ({ type: CHANGE_NAME, name }) const changeAgeAction = (age) => ({ type: CHANGE_AGE, age }) module.exports = { changeNameAction, changeAgeAction }
新建 store/reducer.js 存放 reducer 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const { CHANGE_AGE, CHANGE_NAME } = require('./constants') // 初始化数据 const initialState = { name: 'aaa', age: 18 } function reducer(state = initialState, action) { switch (action.type) { case CHANGE_NAME: return { ...state, name: action.name } case CHANGE_AGE: return { ...state, age: action.age } default: return state } } module.exports = reducer
store/index.js
1 2 3 4 5 6 const { createStore } = require('redux') const reducer = require('./reducer') const store = createStore(reducer) module.exports = store
main.js
1 2 3 4 5 6 7 8 9 10 11 const store = require('./store/index') const { changeNameAction, changeAgeAction } = require('./store/actionCreatore') // 当store中的数据发生改变 会自动回调这个函数 store.subscribe(() => { console.log(store.getState()) }) //派发一个action store.dispatch(changeNameAction('bbb')) store.dispatch(changeAgeAction(20))
6.2.1 基本使用
安装
1 npm install @reduxjs/toolkit react-redux
新建 store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { configureStore } from "@reduxjs/toolkit" ;import counterReducer from "./features/counter" ;const store = configureStore ({ reducer : { counter : counterReducer, }, }); export default store;
新建 store/features/counter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { createSlice } from "@reduxjs/toolkit" ;import { createGlobalStyle } from "styled-components" ;const counterSlice = createSlice ({ name : "counter" , initialState : { counter : 666 , }, reducers : { addNumberAction (state, { payload } ) { state.counter = state.counter + payload; }, subNumberAction (state, { payload } ) { state.counter = state.counter - payload; }, }, }); export const { addNumberAction, subNumberAction } = counterSlice.actions ;export default counterSlice.reducer ;
src/main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 import React from "react" ;import ReactDOM from "react-dom/client" ;import App from "./App" ;import { Provider } from "react-redux" ;import store from "../src/store" ;ReactDOM .createRoot (document .getElementById ("root" )).render ( <React.StrictMode > <Provider store ={store} > <App /> </Provider > </React.StrictMode > );
src/App.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import React , { memo } from "react" ;import { shallowEqual, useDispatch, useSelector } from "react-redux" ;import { addNumberAction, subNumberAction } from "./store/features/counter" ;const App = memo (() => { const { counter } = useSelector ( (state ) => ({ counter : state.counter .counter , }), shallowEqual ); const dispatch = useDispatch (); const addCounter = (num ) => { dispatch (addNumberAction (num)); }; const subCounter = (num ) => { dispatch (subNumberAction (num)); }; return ( <div className ="app" > <h2 > Counter: {counter} </h2 > <button onClick ={(e) => addCounter(1)}>+1</button > <button onClick ={(e) => subCounter(1)}>-1</button > </div > ); }); export default App ;
6.3.2 异步操作方式一 在 useEffect 中发送网络请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import { memo, useEffect } from "react" ;import { shallowEqual, useDispatch, useSelector } from "react-redux" ;import axios from "axios" ;import { changeBannersAction } from "./store/features/home" ;const Home = memo (() => { const { banners } = useSelector ( (state ) => ({ banners : state.home .banners , }), shallowEqual ); const dispatch = useDispatch (); useEffect (() => { axios.get ("http://43.143.0.76:3000/banner" ).then ((res ) => { dispatch (changeBannersAction (res.data .banners )); }); }, []); return ( <div className ="home" > {banners.map((item) => { return ( <div key ={item.targetId} > <img src ={item.imageUrl} alt ="" /> </div > ); })} </div > ); }); export default Home ;
6.3.3 异步操作方式二 使用 createAsyncThunk 和 extraReducers 进行异步操作
store/features/home.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import { createSlice, createAsyncThunk } from "@reduxjs/toolkit" ;import axios from "axios" ;export const fetchChangeBannersAction = createAsyncThunk ( "home/homeBanners" , async () => { const res = await axios.get ("http://43.143.0.76:3000/banner" ); return res.data .banners ; } ); const homeSlice = createSlice ({ name : "home" , initialState : { banners : [], }, reducers : {}, extraReducers (builder ) { builder.addCase (fetchChangeBannersAction.pending , (state, action ) => {}); builder.addCase ( fetchChangeBannersAction.fulfilled , (state, { payload } ) => { state.banners = payload; } ); builder.addCase (fetchChangeBannersAction.rejected , (state, action ) => {}); }, }); export default homeSlice.reducer ;
Home.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import { memo, useEffect } from "react" ;import { shallowEqual, useDispatch, useSelector } from "react-redux" ;import { fetchChangeBannersAction } from "./store/features/home" ;const Home = memo (() => { const { banners } = useSelector ( (state ) => ({ banners : state.home .banners , }), shallowEqual ); const dispatch = useDispatch (); useEffect (() => { dispatch (fetchChangeBannersAction ()); }, []); return ( <div className ="home" > {banners.map((item) => { return ( <div key ={item.targetId} > <img src ={item.imageUrl} alt ="" /> </div > ); })} </div > ); }); export default Home ;
6.3.3 异步操作方式三 使用 createAsyncThunk 但不使用 extraReducers
store/features/home.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import { createSlice, createAsyncThunk } from "@reduxjs/toolkit" ;import axios from "axios" ;export const fetchChangeBannersAction = createAsyncThunk ( "home/homeBanners" , async (data, { dispatch }) => { const res = await axios.get ("http://43.143.0.76:3000/banner" ); dispatch (changeBannersAction (res.data .banners )); } ); const homeSlice = createSlice ({ name : "home" , initialState : { banners : [], }, reducers : { changeBannersAction (state, { payload } ) { state.banners = payload; }, }, }); export const { changeBannersAction } = homeSlice.actions ;export default homeSlice.reducer ;
7 react router 6 7.1 基本使用
安装
1 npm install react-router-dom
配置路由模式
BrowserRouter 为 history 模式
HashRouter 为 hash 模式
1 2 3 4 5 6 7 8 9 10 11 12 import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import { HashRouter } from 'react-router-dom' ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <HashRouter> <App /> </HashRouter> </React.StrictMode>, )
配置路由映射和跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import React , { memo } from "react" ;import { Link , Navigate , NavLink , Route , Routes } from "react-router-dom" ;import About from "./pages/About" ;import Home from "./pages/Home" ;const App = memo (() => { return ( <div className ="app" > <div className ="header" > <div className ="nav" > {/* 路由跳转 */} <Link to ="/home" > 首页</Link > <Link to ="/about" > 关于</Link > {/* <NavLink to ='/about' > </NavLink > 选中的会自动加一个.active类名 */} </div > </div > <div className ="content" > {/* 配置映射关系 */} <Routes > <Route path ="/home" element ={ <Home /> } /> <Route path ="/about" element ={ <About /> } /> {/* Navigate 用于路由重定向 */} <Route path ="/" element ={ <Navigate to ="/home" /> }></Route > </Routes > </div > <div className ="footer" > footer</div > </div > ); }); export default App ;
7.2 嵌套路由 1 2 3 4 5 6 7 8 9 10 11 12 <div className='content'> {/* 配置映射关系 */} <Routes> {/* 嵌套路由 */} <Route path='/home' element={<Home />}> <Route path='/home/recommend' element={<HomeReacommend />} /> </Route> <Route path='/about' element={<About />} /> {/* Navigate 用于路由重定向 */} <Route path='/' element={<Navigate to='/home' />}></Route> </Routes> </div>
配置 Outlet 作为子路由出口
1 2 3 4 5 6 7 8 9 10 11 12 import React, { memo } from 'react' import { Outlet } from 'react-router-dom' const Home = memo(() => { return ( <div> <Outlet /> </div> ) }) export default Home
7.3 编程式路由导航 useNavigate()
返回一个函数,调用该函数实现编程式路由导航。函数有两种参数传递方式。
第一种方式传递两个参数:路由和相关参数。参数只能设置 replace
和 state
。想要传递 params
和 search
参数直接在路由带上。
第二种方式传递数字,代表前进或后退几步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const navigate = useNavigate() const toHome = () => { navigate('/home') } const toAbout = () => { navigate('/about', { replace: true }) } const forword = () => { navigate(1) } const back = () => { navigate(-1) }
7.4 路由传参 7.4.1 传递 params 参数 传递参数
1 2 3 4 5 const [id, setId] = useState(100) const toAbout = () => { navigate(`/about/${id}`) } <Route path='/about/:id' element={<About />} />
获取参数
1 2 const {id} = useParams() console.log(id) // {id: 100}
7.4.2 传递 search 参数 传递参数
1 2 3 4 5 6 const [id, setId] = useState(100) const [name, setName] = useState('aaa') const toAbout = () => { navigate(`/about?id=${id}&name=${name}`) } <Route path='/about' element={<About />} />
获取参数
1 2 3 4 5 // 数组的解构赋值 const [searchParams, setSearchParams] = useSearchParams() // 需要调用 get() 方法获取对应的参数值 const id = searchParams.get('id') const name = searchParams.get('name')
7.4.3 传递 state 参数 传递参数
1 2 3 4 5 6 7 8 9 const toAbout = () => { navigate('/about', { state: { id: 100, name: 'aaa' } }) } <Route path='/about' element={<About />} />
获取参数
1 2 3 4 const { state } = useLocation() console.log(state.id) console.log(state.name)
7.5 路由表 新建 router/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import { Navigate } from 'react-router-dom' import About from '../pages/About' import Home from '../pages/Home' import HomeReacommend from '../pages/HomeReacommend' const routes = [ { path: '/', element: <Navigate to='/home' /> }, { path: '/home', element: <Home />, children: [ { path: '/home', element: <Navigate to='/home/recommend' /> }, { path: '/home/recommend', element: <HomeReacommend /> } ] }, { path: '/about', element: <About /> } ] export default routes
App.jsx 导入配置
1 2 3 import routes from "./router" ;import { useRoutes } from "react-router-dom" ;<div className ="content" > {useRoutes(routes)}</div > ;
7.6 路由懒加载 router/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import React from 'react' import { Navigate } from 'react-router-dom' const About = React.lazy(() => import('../pages/About')) const Home = React.lazy(() => import('../pages/Home')) const HomeReacommend = React.lazy(() => import('../pages/HomeReacommend')) const routes = [ { path: '/', element: <Navigate to='/home' /> }, { path: '/home', element: <Home />, children: [ { path: '/home', element: <Navigate to='/home/recommend' /> }, { path: '/home/recommend', element: <HomeReacommend /> } ] }, { path: '/about', element: <About /> } ] export default routes
src/main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import React, { Suspense } from 'react' import ReactDOM from 'react-dom/client' import App from './App' import { HashRouter, BrowserRouter } from 'react-router-dom' ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <HashRouter> <Suspense fallback='loading'> <App /> </Suspense> </HashRouter> </React.StrictMode>, )
8 hooks