重学React
重学React
基础部分
1 | <script type="text/babel"> |
jsx基础语法
定义虚拟dom不能使用”“
标签中混入js表达式使用{}
样式类名使用className
内联样式使用双大括号包裹
js表达式与js语句:
表达式返回一个值,如a a+b fun() arr.map() function fun(){};
语句是js代码,不会返回值,如if(){} while(){} for(){} switch(){}
1 | const VDOM = ( |
组件
组件名称必须以大写字母开头
函数式组件
1 | <script type="text/babel"> |
Class组件
1 | class Welcome extends React.Component { |
组件三大属性
state
1 | <script type="text/babel"> |
注意:
- 构造函数需要传递一个props参数
- 关注this,所有方法都是严格模式,直接调用this就为undefined
- 改变state需要用setState,修改state部分属性是合并不是覆盖
- this.setState(),接收两种参数:
- setState(stateChange, [callback]) // callback可选回调,在render后调用
- setState(updater, [callback]) // updater为返回state对象的函数,可以接收state和props参数
React控制之外的事件调用setState是同步更新,如原生事件setTimeout/setInterval;大部分开发用到的React封装的事件,如onChange/onClick,setState是异步处理的
同步更新一个setState调用一次render,异步更新多个setState统一调用一次render
1 | <script type="text/babel"> |
调用方法传递参数,有两种方法:
1 | <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> |
props
props主要用来传递数据
基本使用:
1 | <script type="text/babel"> |
对象使用:
1 | <script type="text/babel"> |
props限制:
1 | class Con extends React.Component { |
函数式组件的使用:
1 | // 作为参数传递: |
refs
字符串形式
1
2
3
4<input type="text" ref="input" onBlur={() => this.inputBlur()}/>
inputBlur() {
console.log(this.refs.input.value);
}回调形式
在ref属性中添加一个回调,将dom作为参数传递,c是该input标签,把元素赋值实例对象一个属性
1
2
3
4<input type="text" ref={c => this.input = c} onBlur={() => this.inputBlur()}/>
inputBlur() {
console.log(this.input.value);
}api形式
1
2
3
4
5
6
7
8
9MyRef = React.createRef()
render() {
return (
<input type="text" ref={this.MyRef} onBlur={() => this.inputBlur()}/>
)
}
inputBlur() {
console.log(this.MyRef.current.value);
}
尽可能使用操作元素事件替代,少用ref
react事件
react事件通过onXXX属性指定属性处理函数,通过事件委托方式处理,事件中返回函数,通过event.target得到发生事件的dom元素对象
受控与非受控组件
受控组件state是”唯一数据源”;
非受控组件数据不会更新state,数据用作展示,输入数据现用现取
高阶函数
- 函数的参数是函数
- 函数的返回值是一个函数
函数的柯里化
通过函数调用继续返回函数的形式,实现多次接收参数最后统一处理的函数编码形式
生命周期
旧
初始化阶段,由ReactDom.render()触发
i. constructor()
ii. componentWillMount()
iii. render()
iv. componentDidMount()
更新阶段,由this.setState()触发
i. shouldComponentUpdate() // 返回true/false判断是否更新
ii. componentWillUpdate()
iii. render()
(iv. componentWillReceiveProps) // 父组件更新,子组件先执行(第一次传递数据不执行)
iv. componentDidUpdate()
卸载组件,由ReactDOM.unmountComponentAtNode()触发
i. componentWillUnmount()
新
抛弃componentWillMount/componentWillReceiveProps/componentWillUpdate
初始化阶段,由ReactDOM.render()触发
i. constructor()
ii. getDerivedStateFromProps // 必须static 传参(props,state),返回Null或state对象
iii. render()
iv. componentDidMount()
更新阶段,由this.setState()或父组件重新render触发
i. getDerivedStateFromProps
ii. shouldComponentUpdate()
iii. render()
iv. getSnapshotBeforeUpdate // 传参(prevProps, prevState) ,任何返回值传给v阶段
v. componentDidUpdate() // 传参(prevProps, prevState, snapshot)
卸载组件, 由ReactDOM.unmountComponentAtNode()触发
i. componentWillUnmount()
react 跨域
方法一
在package.json中配置:
"proxy": "http://localhost:5000"
方法二
1
2
3
4
5
6
7
8
9
10"proxy": {
"/api": {
"target": "http://localhost:8000",
"changeOrigin": true
},
"/app": {
"target": "http://localhost:8001",
"changeOrigin": true
}
}方法三
创建src/setupProxy.js
1
2
3
4
5
6
7
8
9
10
11
12const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api',{
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
})
)
}
路由
基本使用:
1 | <Link to="/hello">Hello</Link> |
路由组件与一般组件:
- 写法不一样:
- 存放位置不一样:一般组件components,路由组件pages
- 路由组件接收到固定三个属性history,location,match
NavLink封装:
1 | // 通过{...对象}的形式解析对象,相当于将对象中的属性全部展开 |
嵌套路由:
1 | v6: |
其他参考:react-router v6迁移指南 React-Router v6 新特性解读及迁移指南_前端劝退师-CSDN博客_react router v6](https://blog.csdn.net/weixin_40906515/article/details/104957712))
向路由组件传递参数
params
- 路由链接:
<Link to='/demo/test/tom/18'}>详情</Link>
- 注册路由:
<Route path="/demo/test/:name/:age" component={Test}/>
- 接收参数:this.props.match.params
1
2
3
4
5
6
7
8
9
10-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<hr />
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
const {id,title} = this.props.match.params- 路由链接:
search
- 路由链接:
<Link to='/demo/test?name=tom&age=18'}>详情</Link>
- 注册路由(无需声明):
<Route path="/demo/test" component={Test}/>
- 接收参数:this.props.location.search
1
2
3
4
5
6
7
8
9
10
11
12
13-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
<hr />
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
import qs from 'querystring'
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))- 路由链接:
state
- 路由链接:
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
- 注册路由(无需声明):
<Route path="/demo/test" component={Test}/>
- 接收参数:this.props.location.state
- 使用
BrowserRouter
刷新才可以保留住参数
,使用HashRouter
刷新后state将会没有history
来保存参数
- 使用
1
2
3
4
5
6
7
8
9
10
11
12-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
<hr />
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
// 接收state参数,后面添加`||{}`是防止使用`HashRouter`后state为undefined时报错
const {id,title} = this.props.location.state || {}- 路由链接:
编程式路由导航
v5借助this.props.history对象的api对路由跳转进行操作
- this.props.history.push()
- this.props.history.replace()
- this.props.history.goBack()
- this.props.history.goForward()
- this.props.history.go()
1 | pushShow = (id, title) => { |
v6使用useNavigate进行编程式导航
withRouter
withRouter可以让一般组件须具备路由组件的属性export default withRouter(Header)
redux
中文文档:Redux中文文档
redux是什么
- redux是一个专门用于做
状态管理的JS库
(不是react插件库)。 - 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
- 作用: 集中式管理react应用中多个组件
共享
的状态。
redux三个概念
action
动作的对象。
type: 标识属性,唯一,值为字符串
data: 数据属性,值任意
例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
reducer
用于初始化状态、加工状态。
加工时根据旧的state和action,产生新的state的纯函数
redux的reducer函数必须是一个纯函数
纯函数:相同输入必定得到同样输出。
- 不得改写参数数据
- 不会产生任何副作用,如网络请求、输入输出设备
- 不能调用Date.now()或Math.random()等不纯的方法
store
将state、action、reducer联系在一起的对象
如何获得?
1
2
3import { createStore } from 'redux'
import reducer from './reducers'
const store = createStore(reducer)对象的功能?
- getState():得到state
- dispatch(action):分发action,触发reducer调用,产生新的state
- subscribe(listencer):注册监听,产生新的state时,自动调用
redux的核心api
- createstate()与applyMiddleware()
createstore():创建包含指定reducer的store对象
applyMiddleware():应用基于redux的中间件
1 | import { createStore, applyMiddleware } from 'redux' |
- store对象
redux最核心的管理对象。
维护state,reducer
1 | // ---------------------------store.js--------------------------------- |
- combinReducers()
作用:合并多个reducer函数
1 | // ------------------ redux/reducers/index.js ------------------------------------ |
redux异步编程
使用异步中间件:
下载依赖npm i redux-thunk
react-redux
react插件库,用来简化react应用中使用redux
react-redux将组件分成两大类:ui组件与容器组件
- ui组件
- 只负责ui呈现,不带有任何业务逻辑
- 通过props接收数据
- 不使用任何redux的api
- 一般保存在components文件夹下,也可以直接在容器组件中直接加工成容器组件
- 容器组件
- 负责管理数据和业务逻辑
- 使用redux的api
- 一般保存在containers文件夹下
相关api
provider
作用:记所有组件都可以得到state数据
1
2
3
4
5
6
7
8
9
10
11
12
13import React from 'react'
import ReactDOM from "react-dom"
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
ReactDOM.render(
/* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)connect()()
作用:用于包装ui组件生成容器组件
connect(mapStateToProps,mapDispatchToprops)(ui组件)
注意:
- 默认传入state与dispatch
- 省略dispatch直接传入action,自动调用dispatch
mapStateToProps
将外部数据state对象转换为ui组件的标签属性,返回一个对象
返回对象的key就是传递给ui组件的props的key,value就作为传递ui组件props的value
用于传递状态
1
2
3function mapStateToProps(state) {
return { count: state }
}mapDispatchToProps
将分发的action的函数转换为ui组件的标签属性,返回一个对象
用于传递操作状态的方法
1
2
3
4
5
6
7
8
9
10
11
12
13function mapDispatchToProps(dispatch) {
return {
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
// ------- 简化代码 -----
export default connect(
state => ({ count: state.count, personCount: state.person.length }),
{ increment, decrement, incrementAsync }
)(Count)
求和案例
redux mini版
去除自身状态
src下建立:
-redux
-store.js
-count_reducer.js
store.js
1
2
3
4import { createStore } from 'redux'
import countReducer from './count_reducer'
export default createStore(countReducer)count_reducer.js
1
2
3
4
5
6
7
8
9
10
11
12const initState = 0
export default function countReducer(preState=initState, action) {
const { type, data } = action;
switch (type) {
case 'increment':
return preState + data
case 'decrement':
return preState - data
default:
return preState
}
}count/index.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13import store from '../../redux/store'
increment = () => {
const { value } = this.selectNumber
store.dispatch({ type: 'increment', data: value*1})
}
incrementIfOdd = () => {
const { value } = this.selectNumber;
const count = store.getState();
if (count % 2 !== 0) {
store.dispatch({ type: 'increment', data: value * 1 });
}
}
<h1>当前求和为: {store.getState()}</h1>index.js
1
2
3
4
5
6
7
8import store from './redux/store'
ReactDOM.render(<App />, document.getElementById('root'))
// 监听
store.subscribe(() => {
ReactDOM.render(<App />, document.getElementById('root'))
})
redux 完整版
新增文件:
count_action.js
1
2
3import { INCREMENT, DECREMENT } from './constant'
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })constant.js
1
2export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
redux 异步action
延迟动作不想给组件自身,交给action管理
action返回一般对象为同步action,返回函数是异步action
使用redux-thunk,配置在store中
修改count_action.js
1
2
3
4
5
6
7
8// 异步action指action值为函数,异步action中一般会传入同步action,参数值为dispatch, 不是必须用的,在action中写异步时用,在组件中写异步方法不用
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time);
}
}修改store.js
1
2
3
4
5import { createStore, applyMiddleware } from 'redux'
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
export default createStore(countReducer, applyMiddleware(thunk))修改count/index.jsx
1
2
3
4incrementAsync = () => {
const { value } = this.selectNumber;
store.dispatch(createIncrementAsyncAction(value * 1, 500));
}
react-redux基础版
- ui组件内不能使用任何redux的api,只负责页面呈现
- 容器组件:只负责和redux通信,将结果交给ui组件
- 创建容器组件:react-redux的connect()(ui)方法
- 容器组件中store是靠props传进去的
App.jsx
1 | // 导入容器组件 |
添加容器组件containers/Count/index.js
1 | import CountUI from '../../components/Count' |
修改ui组件components/Count/index.js
1 | increment = () => { |
react-redux优化
优化点:
- 容器组件和ui组件合并为一个文件
- 无需自己给容器组件传递store,给
<App/>
包裹一个<Provider store={store}>
即可 - 使用react-redux后不用自己监听redux中状态的改变
- mapDispatchToProps可以简单的写成一个对象
- 一个组件使用redux经过哪几步?
- 定义好ui组件
- 引入connect生成一个容器组件并暴露
- 在ui组件中通过this.props.xxx读取和操作状态
container/Count/index.js
1 | export default connect( |
src/index.js
使用react-redux无需再去监听redux状态改变
提供Provider给所有需要store的组件传入store
1 | import { Provider } from 'react-redux' |
react-redux数据共享版
- 定义Person组件,与Count组件通过redux共享数据
- 为Person组件编写reducer,action,配置constant常量
- 使用combineReducer进行reducer合并
- 交给store的是总reducer,取出状态时取到位
containers/Person/index.js
1 | import React, { Component } from 'react'; |
actions/person.js
1 | import { ADD_PERSON } from '../constant' |
reducer/person.js
1 | import { ADD_PERSON } from "../constant"; |
redux/store.js
1 | import { createStore, applyMiddleware, combineReducers } from 'redux' |
redact-redux开发者工具
redux/store.js
1 | import { composeWithDevTools } from 'redux-devtools-extension' |
打包
npm run build
打包项目
全局安装npm i serve
,serve 项目目录测试运行。
或者搭建后台环境执行。
例nodejs环境:
1 | const express = require('express') |
react 拓展
setState的2种写法
setState(stateChange, [callback])
stateChange是一个对象,callback是render后的回调
setState(updater, [callback])
updater是一个函数,接收state和props两个参数,返回对象
lazyLoad
路由组件lazyLoad
1 | import React, { Component,lazy,Suspense} from 'react' |
Hooks
useState()
1
2
3
4
5
6
7
8State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
语法: const [xxx, setXxx] = React.useState(initValue)
useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值useEffect
1
2
3
4
5
6
7
8
9Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React中的副作用操作:发ajax请求数据获取、设置订阅 / 启动定时器、手动更改真实DOM
语法:
userEffect(() => {
// 执行副操作
return () => { // 写了这里在组件卸载前执行
}
},[statevalue]) // statevalue值变化引起执行,为空只初始化执行
可以把 useEffect Hook 看做如下三个函数的组合:componentDidMount()、componentDidUpdate()、componentWillUnmount()useRef
1
2
3Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
语法: const refContainer = useRef()
作用:保存标签对象,功能与React.createRef()一样
Fragment
1 | <Fragment><Fragment> |
Context
一种组件间通信方式, 常用于【父组件】与【后代组件】间通信
1 | // 父组件 |
组件优化
- 重写shouldComponentUpdate()方法
- 使用PureComponent
render props
向组件内部动态传入带内容的结构(标签)
使用children props: 通过组件标签体传入结构
1
2
3
4
5<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到render props
1
2
3<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
错误边界
用来捕获后代组件错误,渲染出备用页面
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误
1 | // 生命周期函数,一旦后台组件报错,就会触发 |