[[TOC]]
npm i create-react-app -g
安装脚手架(需要node14以上)
create-react-app my-app
创建项目
用vscode打开项目my-app
npm start
运行项目
win7安装node14:
若安装有nvm, 先卸载
下载node14解压到c盘
右键我的电脑 => 属性 => 高级系统设置 => 环境变量 => 系统变量 => 双击Path
查看有Path里node的路径, 将路径改为
C:\node-v14.15.3-win-x64;NODE_SKIP_PLATFORM_CHECK=1
npm i react-router-dom
安装路由插件// App.js
import { BrowserRouter as Router, Routes, Route} from "react-router-dom";
import Home from "./pages/home/index";
import Demand from "./pages/demand/index";
function App() {
return (
<div className="app">
<Router>
<Routes>
<Route path="/house" element={<Home />}>房子</Route>
<Route path="/demand" element={<Demand />}> 需求 </Route>
</Routes>
</Router>
</div>
);
}
export default App;
// App.js配置
import { BrowserRouter as Router, Routes, Route} from "react-router-dom";
import Home from "./pages/home/index";
// Home子路由组件
import Detail from "./pages/home/detail";
import List from "./pages/home/list";
import Demand from "./pages/demand/index";
function App() {
return (
} />
}>
{/* 嵌套路由(子路由)配置 */}
} />
} />
}> 需求
);
}
export default App;
// home.jsx 父路由
import { Outlet } from "react-router-dom";
function Index() {
return (
父组件
);
}
export default Index;
react-router-dom v6以前的版本, 有一个Redirect组件可以实现
v6的重定向, 官方给出的参考是
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
function Redirect({ to }) {
let navigate = useNavigate();
useEffect(() => {
navigate(to);
});
return null;
}
// usage
} />
} />
}
/>
;
npm install sass
npm i less less-loader@6
less-loader版本太高则不兼容// 1. 在64行中,将
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
修改成下面的代码(若想保留sass则复制后再修改)
const lessRegex = /\.(less)$/;
const lessModuleRegex = /\.module\.(less)$/;
// 2. 在502行, 将sass修改为less, 如果想保留sass就复制一份出来进行修改
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
官网地址
// 父组件
import React from 'react';
import Son from './Son'
class Father extends React.Component{
constructor() {
super();
this.getMsg = this.getMsg.bind(this);
}
// 接收子组件传过来的数据
getMsg(data) {
console.log(data);
}
render() {
return(
<div>
<h3>父组件</h3>
<hr/>
{/* 给子组件传递属性和方法 */}
<Son msg="hello son" getMsg={this.getMsg}/>
</div>
)
}
}
export default Father;
// 子组件
import React from 'react';
class Son extends React.Component{
constructor() {
super();
}
render() {
let {msg,getMsg} = this.props;
return(
<div>
{msg}<br/>
{/* 调用父组件传过来的方法,并传递数据 */}
<button onClick={()=>{getMsg('哈哈哈哈哈')}}>发送消息</button>
</div>
)
}
}
export default Son;
import axios from "axios";
let isDev = process.env.NODE_ENV === 'development';
let baseURL;
if (isDev) {
baseURL = "http://localhost:3006";
} else {
baseURL = "http://huruqing.cn:3006";
}
const service = axios.create({
baseURL,
timeout: 10 * 60 * 1000,
});
//4. 请求拦截
service.interceptors.request.use(
(config) => {
// do something
return config;
},
(err) => {
return Promise.reject(err);
}
);
service.interceptors.response.use(
(res) => {
const data = res.data;
if (data.code != 666) {
return Promise.reject(data.msg);
}
return data;
},
(err) => {
return Promise.reject(err);
}
);
const get = (url, data = {}) => {
return service.get(url, { params: data });
};
const post = (url, data = {}) => {
return service.post(url, data);
};
export default {
get,
post,
};
在React项目使用短路径, 跟 …/…/…/…/地狱模式说拜把
项目的根目录下创建jsconfig.json
文件,并添加以下代码。
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
// 使用
import axios from "../../../utils/request"; // 以前的写法
import axios from "utils/request";
最佳实践: 写静态的时候, 如果图片来自网上, 随便用网上的一张图片暂代, 等 拿到数据再使用变量代替
方式1: 支持完整路径
<img sr="https://mall.s.maizuo.com/aaa.png"/>
方式2: 使用import
import bg from 'assest/img/bg.png'
<img src={bg} alt=""/>
{
list.map(item=>(
<ul>
<li>11111li>
<li>22222li>
ul>
))
}
// 类似vue的template的做法
{
list.map(item=>(
<>
<div>11111div>
<div>22222div>
>
))
}
let htmlStr = ``
export default function() {
return (
<div className="pt-15 bg-fff" dangerouslySetInnerHTML={{ __html:htmlStr}}></div>
)
}
类似vue绑定class的操作
// 判断条件为true时会给div添加class类 on
div>
// 实际应用
import React from 'react';
import classnames from 'classnames';
export default function() {
let curr = '01';
return(
<div className="demo-index">
111li>
222li>
333li>
div>
)
}
(九) Hooks
react hooks是什么
- 在函数组件中没有state(状态), 也没有生命周期
- react hooks是react中引入新特性,它可以让react函数组件也拥有状态
(1) useState
- 在函数组件中使用useState来定义响应式数据, 接收一个参数(变量的默认值)
setKeyword
相当于之前的setState
, 用来修改keyword的值
import React, { useState } from 'react';
export default function() {
// 定义响应式数据keyword,keyword的默认值是 ''
let [keyword,setKeyword] = useState('');
return(
<div>
<button onClick={()=>{setKeyword('哈哈哈哈哈')}}>修改关键字</button>
<p>
{keyword}
</p>
</div>
)
}
(2) useEffect
- 中括号内的变量发生了改变, 就会触发回调函数
- 请求后台接口可以放在useEffect的函数里执行, 中括号为空或者不要中括号
let [keyword,setKeyword] = useState('');
useEffect(()=> {
console.log(2222);
// 当keyword发生变化时,会触发回调函数
},[keyword])
(3) useHistory
用来进行路由跳转
(十) 路由跳转和传参
路由跳转:
1. 使用 <Link to="xxx" />
2. 使用history
- 如果是class组件, 使用this.props.history.push();
- 函数组件则使用hooks
import {useHistory} from 'react-router';
let history = useHistory();
history.push();
(1) 动态路由传参
{props.match.params.id}
// 从其他页面跳转到 /demo
import { useHistory } from "react-router-dom";
export default function () {
const history = useHistory();
const goto = () => {
history.push("/demo/2222");
};
return (
<div>
<button onClick={goto}>动态路由button>
<Link to="/demo/22222" />
div>
);
}
// 路由配置 app.js
<Route path="/demo/:id" component={Demo} />
// demo.jsx
import React from 'react';
export default function(props) {
console.log(props);
return(
<div className="demo-index">
动态路由参数: {props.match.params.id}
div>
)
}
(2) query传参(页面刷新后参数不存在)
// 核心代码
history.push({
pathname: '/my',
query:{
username: 'zs',
age: 100
}
})
// 接收
props.location.query;
// app.js的路由配置
<Route path="/demo" component={Demo} />
// 其他页面
import { useHistory } from "react-router-dom";
export default function () {
const history = useHistory();
const goto = () => {
history.push({
pathname: '/demo',
query: {
username: '法外狂徒'
},
state: {
age: 1000
}
});
};
return (
<div>
<button onClick={goto}>query&statebutton>
div>
);
}
// 接收参数
// query传参刷新页面就没有了,要使用&&以防报错
import React from "react";
export default function (props) {
let { query, state } = props.location;
return (
<div className="demo-index">
<p> query参数: {query && query.username}p>
<p> state参数: {state.age}p>
div>
);
}
(3) state传参(页面刷新后参数还存在)
// 核心代码
history.push({
pathname: '/my',
state:{
username: 'zs',
age: 100
}
})
// 接收
props.location.state;
(4) search传参
url后拼接参数, 通过props.location.serach接收, 需要处理参数
(九) hooks
函数组件中没有state, 也没有生命周期, hooks可以在函数组件中使用state
(1) useState
- 在函数组件中使用useState来定义响应式数据, 接收一个参数(变量的默认值)
setKeyword
相当于之前的setState
, 用来修改keyword的值
import React, { useState } from 'react';
export default function() {
// 定义响应式数据keyword,keyword的默认值是 ''
let [keyword,setKeyword] = useState('');
return(
<div>
<button onClick={()=>{setKeyword('哈哈哈哈哈')}}>修改关键字</button>
<p>
{keyword}
</p>
</div>
)
}
(2) useEffect
- 中括号内的变量发生了改变, 就会触发回调函数
- 请求后台接口可以放在useEffect的函数里执行, 中括号为空或者不要中括号
let [keyword,setKeyword] = useState('');
useEffect(()=> {
console.log(2222);
// 当keyword发生变化时,会触发回调函数
},[keyword])
(3) useHistory
可以让函数组件拥有使用history的功能(跳转的时候需要)
(十) redux
本例gitee地址: [email protected]:huruqing/react-redux-demo.git
新版redux,使用hooks https://react-redux.js.org/tutorials/quick-start
(1) redux使用场景
mobx也是用来进行跨组件通信, 比redux更简单一些
一句话: 状态共享, 跨组件通信
某个组件的状态,需要共享
某个状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态
(2) 核心概念
- **Store: **Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
Redux 提供createStore这个函数,用来生成 Store。
- **State: **状态, 存放在store里
- Action
- action是个对象, 描述当前发生的事情
- Action Creator: View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。
- 派发action: store.dispatch() 是 View 发出 Action 的唯一方法。
- Reducer
- Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
- Reducer 是一个纯函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。上面的createStore接受 Reducer 作为参数,生成一个新的 Store
- 纯函数: 纯函数是函数式编程的概念,必须遵守以下一些约束。
- 特点: 相同的输入会有相同的输出
- 不得改写参数
- 不能调用系统 I/O 的API(就是输入输出)
- 不能调用
Date.now()
或者Math.random()
等不纯的方法,因为每次会得到不一样的结果
(3) 项目中redux步骤
知识目录
- 创建store
- 获取状态
- 修改状态
- 模块化
- 持久化
1. 安装redux和react-redux
npm i redux react-redux
2. 创建store和相关的东西
- src下新建 /redux/index.js
- 初始化state、声明action、声明reduer
- 创建store并导出
import {createStore} from 'redux';
// 1.state的初始值
const initState = {
count: 0
}
// 2.声明action creator 并导出,payload是派发action时传进来的数据
export const updateCount = function(payload) {
// 此函数返回一个action
return {
type: 'UPDATE_COUNT',
payload,
}
}
// 3.声明reducer
const reducer = function(state=initState,action) {
let {type,payload} = action;
switch(type) {
case 'UPDATE_COUNT':
return {
...state,
count: state.count+payload
}
default:
return state;
}
}
let store = createStore(reducer);
export default store;
3. 挂载store
// index.js(src目录下的)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
import store from 'redux/index'
ReactDOM.render(
<React.StrictMode>
// 挂载store
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
4. 获取和修改store的状态
把普通react组件改造成react高阶组件
import React from "react";
// 1 导入action creator等相关东西
import { updateCount } from "redux/index";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
function Demo(props) {
return (
<div>
{/* 5.获取count的值 */}
<p>count的值: {props.count}</p>
{/* 修改count的值 */}
<button onClick={()=>{props.updateCount(props.count+1)}}>修改count</button>
</div>
);
}
// 2.把store里state转变成props来使用,后面访问state时就使用props.xxx来进行访问
// @params state store里面所有状态
function mapStateToProps(state) {
return {
// 组件使用props.count来访问count状态
count: state.count
}
}
// 3.把dispatch转变成props来使用
function mapDispatchToProps(dispatch) {
return {
// bindActionCreators的作用:把action creator和dispatch绑定起来
updateCount: bindActionCreators(updateCount,dispatch)
}
}
// 4.connect方法的作用: 把普通组件Demo变高阶组件
export default connect(mapStateToProps,mapDispatchToProps)(Demo);
(4) redux 模块化
- 新增cart模块, 在里面定义state,action,reducer并导出
// demo模块
// 1.state的初始值
const initState = {
count: 10
}
// 2.声明action creator 并导出,payload是派发action时传进来的数据
export const updateCount = function(payload) {
// 此函数返回一个action
return {
type: 'UPDATE_COUNT',
payload,
}
}
// 3.声明reducer
export default function(state=initState,action) {
let {type,payload} = action;
switch(type) {
case 'UPDATE_COUNT':
return {
...state,
count: payload
}
default:
return state;
}
}
// user模块
// 1.usermok的初始化状态
const initState = {
phone: "",
token: "",
};
// 2.相关action creator
export const updatePhone = function (payload) {
// 返回一个action
return {
type: "UPDATE_PHONE",
payload,
};
};
export const updateToken = function (payload) {
return {
type: "UPDATE_TOKEN",
payload
};
};
// 3.声明reducer
export default function (state = initState, action) {
let { type, payload } = action;
switch (type) {
case "UPDATE_PHONE":
return {
...state,
phone: payload,
};
case "UPDATE_TOKEN":
return {
...state,
token: payload,
};
default:
return state;
}
}
// 合并reducer, /src/redux/index.js
import {createStore} from 'redux';
import demoReducer from './demo';
import userReducer from './user';
import { combineReducers } from 'redux';
// 合并reducer
let reducers = combineReducers({
demo: demoReducer,
user: userReducer
})
export default createStore(reducers);
- 合并reducer, 改造/redux/index.js 代码
import {createStore} from 'redux';
import demoReducer from './demo';
import userReducer from './user';
import { combineReducers } from 'redux';
// 合并reducer
let reducers = combineReducers({
demo: demoReducer,
user: userReducer
})
export default createStore(reducers);
(5) redux 持久化
1. 安装持久化插件 redux-persist
npm i redux-persist
2. 使用插件对store进行持久化操作
改造 /redux/index.js 代码, 对reducer和store进行持久化配置
3. 应用持久化后的store
改造 /src/index.js, 实现持久化
*************************模块化之前持久化**************************
// src/redux/index.js
import { createStore } from "redux";
import {persistStore, persistReducer} from 'redux-persist';
import storageSession from 'redux-persist/lib/storage/session';
// 初始状态
const initState = {
count: 0,
};
export const updateCount = (payload) => {
return {
type: "UPDATE-COUNT",
payload,
};
};
// 修改状态的一个方法, 叫reducer, 当用户派发了一个action, 不管是什么action, 这里都会接收到
const reducer = (state = initState, action) => {
let { type, payload } = action;
switch (type) {
case "UPDATE-COUNT":
return {
...state,
count: state.count + payload,
};
break;
default: {
return state;
}
}
};
const storageConfig = {
key: 'root', // 必须有的
storage:storageSession, // 缓存机制
blacklist: [] // reducer 里不持久化的数据,除此外均为持久化数据
}
const newReducer = persistReducer(storageConfig, reducer);
export let store = createStore(newReducer);
export let newStore = persistStore(store);
/**
* 模块之前的持久化
* src/index.js
*/
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "antd-mobile/dist/antd-mobile.css";
import { Provider } from "react-redux";
import {store,newStore} from "./redux/index";
import { PersistGate } from "redux-persist/lib/integration/react";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate persistor={newStore}>
<App />
</PersistGate>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
*************************模块之后的持久化**************************
// src/redux/index.js代码
import {createStore} from 'redux';
import demoReducer from './demo';
import userReducer from './user';
import { combineReducers } from 'redux';
// 1.导入相关插件
// persistStore持久化store, 持久化reducer
import {persistStore, persistReducer} from 'redux-persist';
// storageSession缓存
import storageSession from 'redux-persist/lib/storage/session';
// 2.store持久化设置
const storageConfig = {
key: 'root', // 必须有的
storage:storageSession, // 缓存机制
blacklist: [] // reducer 里不持久化的数据,除此外均为持久化数据
}
// 3.合并reducer
let reducers = combineReducers({
demo: demoReducer,
user: userReducer
})
// 4.给reducer应用持久化配置
const newReducers = persistReducer(storageConfig, reducers);
// 5.创建store
const store = createStore(newReducers);
// 6.对store进行持久化并导出
export const persistor = persistStore(store);
// 7.导出store
export default store;
// src/index.js代码
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
// 8.持久化
import store, { persistor } from "./redux/index";
import { PersistGate } from "redux-persist/lib/integration/react";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
{/* 用持久化后的store包裹 */}
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
(6) 异步action,使用redux-thunk实现
- 安装redux-thunk
npm i redux-thunk
- 改造/redux/index.js
// 核心代码如下
//1.applyMiddleware 加载中间件
import { createStore,applyMiddleware } from "redux";
//2.导入异步中间件thunk
import thunk from 'redux-thunk';
const store = createStore(newReducers,applyMiddleware(thunk));
- 创建异步action
改造/redux/demo.js
// 1.state的初始值
const initState = {
count: 10,
};
// 2.声明action creator 并导出,payload是派发action时传进来的数据
// export const updateCount = function(payload) {
// // 此函数返回一个action
// return {
// type: 'UPDATE_COUNT',
// payload,
// }
// }
export const updateCount = (payload) => {
// 异步action, 允许返回一个函数,使用dispatch派发action
return (dispatch) => {
setTimeout(() => {
dispatch({
type: "UPDATE_COUNT",
payload,
});
}, 1000);
};
};
// 3.声明reducer
export default function (state = initState, action) {
let { type, payload } = action;
switch (type) {
case "UPDATE_COUNT":
return {
...state,
count: statepayload,
};
default:
return state;
}
}
(7) axios拦截器给http请求头添加token
配置后再查看首页的请求是否都带上了token
import axios from 'axios';
// 1.导入redux
import redux from 'redux/index';
const service = axios.create({
baseURL: 'http://132.232.87.95:3006',
timeout: 10 * 60 * 1000 //10分钟
})
service.interceptors.request.use(
(config) => {
// 2.获取store
let store = redux.getState();
// 3.把token放入请求头
config.headers['user-token'] = store.user.token;
return config;
},
(err) => {
return Promise.reject(err);
}
)
service.interceptors.response.use(
(res) => {
const data = res.data;
if (data.code != 666) {
return Promise.reject(data.msg);
}
return data;
},
(err) => {
return Promise.reject(err);
}
)
const get = (url,data={})=> {
return service.get(url,{params: data});
}
const post = (url,data={})=> {
return service.post(url,data);
}
export default {
get,post
};
(十一) Mobx
https://blog.csdn.net/sinat_17775997/article/details/82772760
它由几个部分组成:Actions、State、Computed Values、Reactions
核心:
在整个数据流中,通过事件驱动(UI 事件、网络请求…)触发 Actions,在 Actions 中修改了 State 中的值,这里的 State 既应用中的 store 树(存储数据),然后根据新的 State 中的数据计算出所需要的计算属性(computed values)值,最后响应(react)到 UI 视图层。
(1) 配置
-
安装依赖
npm install mobx
-
package.json添加装饰器支持
{
"scripts": {},
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
],
"presets": [
"react-app"
]
}
}
错误: Support for the experimental syntax ‘decorators-legacy’ isn’t currently enab – 装饰器@
[报错显示:Parsing error: This experimental syntax requires enabling one of the following parser plugin(s): “decorators-legacy”, “decorators”.](https://blog.csdn.net/lfy_wybss/article/details/122079178)
(十二) 性能优化PureComponent和useMemo
- 类(class)组件性能9优化可以使用shouldCompoentUpdate进行优化
// nextProps下一个属性(新的属性),nextState下一个状态(新的状态)
shouldComponentUpdate(nextProps, nextState) {
// 根据新的属性或者状态决定要不要更新页面
if(xxx) {
return true;
} else {
return false
}
}
- 类(class)组件, 可以让其继承React.PureComponent来实现优化
import React from "react";
class Demo extends React.PureComponent {
constructor(props){
super(props);
}
render() {
console.log(this.props);
return (<div>性能优化</div>)
}
}
export default Demo;
- 函数组件可以使用React.memo进行包装以实现优化
import React from "react";
function Scene(props) {
return <div>{props.name}</div>;
}
export default React.memo(Scene);
https://www.jianshu.com/p/b3d07860b778