React全家桶学习笔记
1. React简介
React 是 Facebook 在 2011 年开发的前端 JavaScript 库。它遵循基于组件的方法,有助于构建可重用的UI组件。
它使用虚拟DOM而非真实DOM
虚拟DOM与真实DOM比较:
- 虚拟DOM本质是一个js的对象
- 虚拟DOM比较“轻”,真实DOM比较“重”(自带属性很多)
- 虚拟DOM最终会被转化为真实DOM
2. JSX语法规则
JSX 是JavaScript XML 的简写。是 React 使用的一种文件。
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式时要用{ }
- 样式的类名指定不要用class,要用className
- 内联样式要用**{{key: value}}**形式去写, 外部花括号表示里面要写js表达式, 内层花括号表示对象
- 虚拟DOM必须只有一个根标签
- 标签必须闭合
- 标签首字母
- 若小写字母,则React会将标签转为html同名元素
- 若大写字母开头,则react则去渲染同名组件
|
|
3. React面向组件编程
3.1. 函数式组件
|
|
3.2 类式组件
|
|
4. React组件三大核心属性
4.1. state
状态(state)不可直接更改,需要借助setState进行更新,且更新是合并的形式,不是替换。
setState()以后组件会调用render()重新渲染
|
|
state简化写法:
|
|
4.2. props
props用于外部传值,一但定义不允许在组件内进行更改.
组件标签内以属性定义方式进行传值
|
|
批量传递标签属性:
|
|
props简写形式,标签类型限制,默认属性定义:
|
|
constructor中props问题
|
|
4.3. refs
组件内的html标签可以定义ref属性来标识自己,React会将其收集到组件实例的refs属性内.
利用refs可以获取到ref属性标识的dom节点
不应该过度使用ref
-
字符串形式ref (不推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<script type="text/babel"> // 1. 创建类式组件 class Demo extends React.Component{ render(){ return ( <div> <input ref="input1" type="text" placeholder="点击按钮提示数据" /> <button ref="btn1" onClick={this.showData}>点我提示左侧数据</button> <input ref="input2" type="text" placeholder="失去焦点提示数据" /> </div> ) } showData = ()=>{ console.dir(this.refs.input1); } } // 2. 渲染组件到页面 ReactDOM.render(<Demo />, document.getElementById("test")); </script>
-
回调形式ref
如果回调函数是以内联方式定义的,则在组件更新过程中它会被执行两次,第一次传入参数为null,第二次会传入参数DOM元素.(不是啥大问题)
通过将ref的回调函数定义成class的绑定函数(实例方法)可以避免上述问题.
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
<script type="text/babel"> // 1. 创建类式组件 class Demo extends React.Component{ state = { isHot: true } render(){ console.log(this); return ( <div> <input ref={(currentNode)=>{ // 回调参数为dom节点本身 this.input1 = currentNode; console.log('被调用'); }} type="text" placeholder="点击按钮提示数据" /> <button ref={(currentNode)=>{ // 回调参数为dom节点本身 this.btn1 = currentNode; }} onClick={this.showData}>点我提示左侧数据</button> <input ref={(currentNode)=>{ // 回调参数为dom节点本身 this.input2 = currentNode; }} type="text" placeholder="失去焦点提示数据" /> <br /> <span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}!!</span> </div> ) } showData = ()=>{ console.log(this.input1.value); const { isHot } = this.state; this.setState({isHot: !isHot}); } } // 2. 渲染组件到页面 ReactDOM.render(<Demo />, document.getElementById("test")); </script>
-
createRef
React.createRef()函数调用后返回一个容器, 该容器用于存储ref所标识的dom节点, 需要多少个节点就创建多少个ref容器
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
<script type="text/babel"> // 1. 创建类式组件 class Demo extends React.Component{ /* React.createRef()调用后可以返回一个容器,该容器用于存储ref所标识的dom节点 该容器只允许存一个节点 */ input1 = React.createRef(); render(){ return ( <div> <input ref={this.input1} type="text" placeholder="点击按钮提示数据" /> <button ref="btn1" onClick={this.showData}>点我提示左侧数据</button> <input ref="input2" type="text" placeholder="失去焦点提示数据" /> </div> ) } showData = ()=>{ console.log(this.input1); } } // 2. 渲染组件到页面 ReactDOM.render(<Demo />, document.getElementById("test")); </script>
5. React中的事件处理
-
通过onXxx属性指定事件处理函数(注意大小写)
a. React使用的是自定义(合成)事件,而不是使用原生DOM事件 ————为了更好的兼容性
b. React中的事件是通过事件委托方式处理的(委托给最外层元素) ————为了高效
-
通过event.target得到发生事件的DOM元素对象
|
|
5.1 非受控组件
顾名思义,非受控组件即不受状态控制,获取数据即操作DOM,即用即取
以做一个用户登录表单提交为例
实现方法可以有利用ref获取dom节点,在登录提交时获取对应节点值,但ref不建议过度使用
|
|
5.2 受控组件
受控组件必须要有value和onChange,onChange用于获取value,保存在状态中,在需要时从状态中去获取值
|
|
5.3 函数柯里化
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
上述onChange每一个事件都要绑定一个不同函数,显然面对多条input时会造成代码冗余
因此,可以利用函数闭包,接收数据类型参数,返回真正调用的函数,根据数据类型参数来对对应状态进行赋值
|
|
6. React组件的生命周期
React组件包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用。
6.1. React生命周期(旧)
初始化阶段 —— 由React.render()触发
-
constructor
构造函数构建组件实例
-
组件将要被挂载
componentWillMount(){}
-
初始化渲染
render()
-
组件挂载完毕
componentDidMount(){}
注意:比较常用,一般用于做一些初始化的事,例如:开启定时器,发送网络请求,订阅消息
-
组件即将卸载
componentWillUnmount(){}
注意:比较常用,一般用于做一些收尾的事,例如:关闭定时器,取消订阅消息
-
卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById(“test”))
Update
-
componentWillReceiveProps(props)
组件将要接收到props,触发后续更新
注意:第一次接收到props不会触发
-
setState()
设置state,触发后续更新
-
forceUpdate()
强制更新, 比正常跟新少了一个shouldUpdate
-
组件是否应该更新
shouldComponentUpdate(nextProps, nextState){}
-
组件将要更新
componentWillUpdate(){}
-
组件更新完毕
componentDidUpdate(){}
|
|
6.2. React生命周期(新)
新版本生命周期和旧版本没太大区别,主要区别如下:
- componentWillMount()
- componentWillUpdate()
- componentWillReceiveProps()
以上三个在新版本中需要加上UNSAFE_前缀使用,如UNSAFE_componentWillMount()
这里的UNSAFE不是指安全性,而是指使用这些生命周期函数的代码在未来版本中可能会出现bug,React未来打算实现异步渲染。
同时新增了下列两个生命周期函数:
-
getDerivedStateFromProps()
: 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。返回一个state状态对象或者null,根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。此方法适用于一些极其特殊的时刻,state的值在任何时候都取决于props
-
getSnapshotBeforeUpdate()
: 在更新时调用,最近一次渲染输出(提交到 DOM 节点)之前调用,获取快照,此函数任何返回值都将作为参数传递给componentDidUpdate()
,此方法也不常用
新版Update调用顺序:
getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate(preProps, preState, snapshotValue)
getSnapshotBeforeUpdate()
用法示例:
|
|
7. DOM的diff算法
-
虚拟DOM中key的作用
a. 简单地说,key是虚拟dom对象的标识
b. 当状态中的数据发生变化时,React会根据【新数据】生成【新的虚拟dom】随后,React进行【新虚拟dom】和【旧虚拟dom】的diff比较
比较规则下:
-
旧虚拟dom中找到了与新虚拟dom相同的key
若虚拟dom的内容没变,则直接使用之前的真实dom。
若虚拟dom的内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom
-
就虚拟dom中没有找到与新的虚拟dom相同的key
根据数据创建新的真实dom,随后渲染到页面
-
-
用index作为key可能会引发的问题
a. 若对数据进行:逆序添加,逆序删除等破坏顺序的操作(例如,在数组首位添加删除元素):
会产生没有必要的真实DOM的更新(因为index变了)
b. 若结构中还包含输入类的dom:
会产生错误DOM更新
c. 若仅用于渲染列表用于展示,则用index没有问题,但是数据量大进行逆序操作会引发效率问题
8. React脚手架使用
8.1. 创建项目并启动
-
全局安装
npm install -g create-react-app
-
创建项目
create-react-app hello-react
-
启动项目
npm start
-
打包项目
npm run build
React项目内组件一般用jsx后缀结尾(用js也能用,一般为了区分)
9. React父子组件通信
9.1. 父组件给子组件传递数据
通过props方式传递
9.2. 子组件给父组件传递数据
也是通过props方式,但是父组件要先定义一个修改数据的函数,将该函数传递给子组件,子组件调用函数对父组件数据进行修改
|
|
10. React兄弟组件通信
消息订阅与发布
工具库: PubSubJS
适用于任意组件间通信
|
|
11. React代理配置
-
单个服务器代理
直接在package.json中加上
”proxy“:"http://localhost:xxxx"
-
多代理配置
在src下建立setupProxy.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// CommonJS语法 const {createProxyMiddleware} = require('http-proxy-middleware'); module.exports = function(app){ app.use( createProxyMiddleware('/api1', { // 遇见前缀为 /api1 的请求, 就会触发该代理配置 target: 'http://localhost:5000', // 请求转发给谁 changeOrigin: true, // 控制服务器接受到的请求头中Host字段的值 为true时 服务器收到请求头为localhost:5000 // 为flase时 服务器收到请求头为localhost:3000 pathRewrite: {'^/api1':''} // 重写请求路径 }) ), app.use( createProxyMiddleware('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2':''} }) ) }
12. React路由
12.1. SPA概念
-
单页Web应用(single page web application, SPA)
-
整个应用只有一个完整的页面
-
点击页面中的链接浏览器不会刷新页面,只会做页面的局部更新
-
数据都需要通过ajax获取,并在前端异步展示
12.2. 路由概念
- 一个路由就是一个映射关系(key: value)
- key为路径, value可能是function或者component
路由分类:
-
后端路由
比如node中,value是function,用于处理客户端提交的请求
app.get(key, function(req, res){})
-
前端路由
浏览器路由,value是component,用于展示页面内容
12.3. react-router
react-router包括有Web,Native,Any
在这里学习使用web上的react路由使用,需要下载react-router-dom
-
最基本路由使用
<Link>
标签编写路由路径<Route>
标签注册路由组件最外部需要包一个
<BroserRouter>
或者<HashRouter>
,路由才会生效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
export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 编写路由链接 */} <Link className='list-group-item' to="/about">About</Link> <Link className='list-group-item' to="/home">Home</Link> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> </div> </div> </div> </div> </div> ) } }
1 2
const root = createRoot(document.getElementById('root')); root.render(<BrowserRouter><App></App></BrowserRouter>);
-
NavLink使用
<NavLink>
可以看作是<Link>
的升级版,在路由被激活时,会追加一个激活后的样式,默认追加类名为active
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
export default class App extends Component { render() { return ( <div> <Header/> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 编写路由链接 */} <NavLink activeClassName='active' className='list-group-item' to="/about">About</NavLink> <NavLink activeClassName='active' className='list-group-item' to="/home">Home</NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> </div> </div> </div> </div> </div> ) } }
-
封装NavLink组件
原生NavLink组件每一次编写都要指定activeClassName,className等属性,造成代码冗余,因此需要对其进行封装,每次使用只需要指定to属性即可。
注意: React组件标签间值会自动作为key为
children
的属性传入组件props内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
export default class App extends Component { render() { return ( <div> <Header/> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> </div> </div> </div> </div> </div> ) } }
封装NavLink:
1 2 3 4 5 6 7
export default class MyNavLink extends Component { render() { return ( <NavLink activeClassName='active' className='list-group-item' {...this.props}></NavLink> ) } }
-
Switch使用
如果一个路由路径指定了多个组件,则多个组件会一起展示
用Switch标签包裹后即可实现单一匹配
1 2 3 4 5 6 7 8
<div className="panel-body"> {/* 注册路由 */} <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Route path="/home" component={Test}/> </Switch> </div>
-
多级路由路径下刷新浏览器样式丢失问题
1 2 3 4 5 6 7 8 9
// 1. 采用public绝对路径 <link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css"> // 2.相对路径不要带. <link rel="stylesheet" href="/css/bootstrap.css"> // 3.采用HashRouter const root = createRoot(document.getElementById('root')); root.render(<HashRouter><App></App></HashRouter>);
-
路由的模糊匹配与严格匹配
默认模糊匹配
开启严格匹配:
严格匹配不建议随意开启,有时会导致无法继续二级路由
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
export default class App extends Component { render() { return ( <div> <Header/> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home/a/b">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Switch> <Route exact={true} path="/about" component={About}/> {/* 不开启严格匹配则该路由对NavLink指定的路径将会生效 */} <Route exact={true} path="/home" component={Home}/> </Switch> </div> </div> </div> </div> </div> ) } }
-
Redirect使用
Redirect组件用于重定向,一般用于页面打开时默认展示路由
当Route一个都没有匹配时,前往Redirect指定的路由路径
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
export default class App extends Component { render() { return ( <div> <Header/> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/About"></Redirect> </Switch> </div> </div> </div> </div> </div> ) } }
-
嵌套路由
注意不要随便开启严格模式
嵌套路由是按照路由注册顺序依次进行匹配的
-
向路由组件传递params参数
在定义路由时以模板字符串形式定义参数内容(value);
在注册路由时声明接收params参数,以key形式声明
参数将会保存在路由组件内props属性内
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
export default class Message extends Component { state = { messageArr: [ {id: '001', title: '消息1'}, {id: '002', title: '消息2'}, {id: '003', title: '消息3'}, ] } render() { return ( <div> <ul> { this.state.messageArr.map((message)=>{ return ( <li key={message.id}> {/* 向路由组件传递params参数 */} <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> </li> ) }) } </ul> <hr></hr> {/* 声明接收params参数 */} <Route path="/home/message/detail/:id/:title" component={Detail}></Route> </div> ) } }
-
向路由组件传递search参数
以query的形式在路由路径内声明即可,
参数保存在路由组件props的location内
需要通过qs模块进行解析
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
export default class Message extends Component { state = { messageArr: [ {id: '001', title: '消息1'}, {id: '002', title: '消息2'}, {id: '003', title: '消息3'}, ] } render() { return ( <div> <ul> { this.state.messageArr.map((message)=>{ return ( <li key={message.id}> {/* 向路由组件传递params参数 */} {/* <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> */} {/* 向路由组件传递search参数 */} <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> </li> ) }) } </ul> <hr></hr> {/* 声明接收params参数 */} {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} {/* search参数无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> </div> ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14
export default class Detail extends Component { render() { const {id, title} = qs.parse(this.props.location.search.slice(1)); return ( <div> <ul> <li>{id}</li> <li>{title}</li> <li>Message</li> </ul> </div> ) } }
-
向路由组件传递state参数
以对象形式定义
<Link>
标签内的to属性,声明pathname以及state在路由组件内通过location属性内的state属性获取
参数不会在浏览器地址栏上显示,但是刷新可以正常显示,因为location也是作为hostory属性的一部分
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
export default class Message extends Component { state = { messageArr: [ {id: '001', title: '消息1'}, {id: '002', title: '消息2'}, {id: '003', title: '消息3'}, ] } render() { return ( <div> <ul> { this.state.messageArr.map((message)=>{ return ( <li key={message.id}> {/* 向路由组件传递params参数 */} {/* <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> */} {/* 向路由组件传递search参数 */} {/* <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> */} {/* 向路由组件传递state参数 */} <Link to={{pathname: '/home/message/detail', state:{id:message.id, title:message.title}}}>{message.title}</Link> </li> ) }) } </ul> <hr></hr> {/* 声明接收params参数 */} {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} {/* search参数无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> </div> ) } }
-
push和replace模式
默认路由跳转是push模式,可以在
<Link>
标签内指定开启replace模式1
<Link replace={true} to='/home/message/detail'>{message.title}</Link>
-
编程式路由导航
有时候需要在没有
<Link>
标签的情况下实现路由导航借助路由组件内
this.props.history
属性以编程方式进行路由的跳转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 56 57 58 59 60 61 62 63 64 65
export default class Message extends Component { state = { messageArr: [ {id: '001', title: '消息1'}, {id: '002', title: '消息2'}, {id: '003', title: '消息3'}, ] } render() { return ( <div> <ul> { this.state.messageArr.map((message)=>{ return ( <li key={message.id}> {/* 向路由组件传递params参数 */} <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> <button onClick={()=>{this.pushShow(message.id, message.title)}}>push查看</button> <button onClick={()=>{this.replaceShow(message.id, message.title)}}>replace查看</button> {/* 向路由组件传递search参数 */} {/* <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> */} {/* 向路由组件传递state参数 */} {/* <Link to={{pathname: '/home/message/detail', state:{id:message.id, title:message.title}}}>{message.title}</Link> */} </li> ) }) } </ul> <hr></hr> {/* 声明接收params参数 */} {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} {/* search参数无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> <button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> </div> ) } back = ()=>{ this.props.history.goBack(); } forward = ()=>{ this.props.history.goForward(); } pushShow = (id, title)=>{ // this.props.history.push(`/home/message/detail/${id}/${title}`); // this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`); this.props.history.push(`/home/message/detail`, {id, title}); } // 实现replace跳转到Detail组件 replaceShow = (id, title)=>{ // this.props.history.replace(`/home/message/detail/${id}/${title}`); // this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`); this.props.history.replace(`/home/message/detail/`, {id, title}); } }
-
withRouter使用
只有路由组件才有
this.props.history
属性对于一般组件如果想要实现路由跳转,需要使用
withRouter
函数进行跳转withRouter可以加工一般组件,让以班组间具有路由组件所特有的API,withRouter的返回值是一个新组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class Header extends Component { render() { console.log(this); return ( <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> <button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> </div> </div> ) } back = ()=>{ this.props.history.goBack(); } forward = ()=>{ this.props.history.goForward(); } } export default withRouter(Header)
-
BrowserRouter 和 HashRouter的区别
-
底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本,
HashRouter使用的是URL的哈希值
-
url表现形式不一样
-
刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在hostory对象中
HashRouter刷新后会导致路由state参数丢失
-
12.4. 路由组件与一般组件的区别
- 路由组件会默认收到路由器传递的props:history,location,match
- 两者写法不同
13. redux
redux是一个专门用于做状态管理的JS库(不是react插件库)
作用:集中式管理react应用中多个组件共享的状态
13.1. redux三个核心概念
-
action
- 动作的对象
- 包含两个属性
- type:表示属性,值为字符串,唯一
- data:数据属性,可选
- 例子:{type: “ADD_STUDENT”, data:{name: “J”, age: 18}}
-
reducer
- 用于初始化状态,加工状态
- 加工时,根据旧的state和action,产生新的state的纯函数
-
store
将state,action,reducer联系在一起的对象
13.2. redux核心API
- createstore()
作用:创建包含指定reducer的store对象
- store对象
-
作用: redux库最核心的管理对象
-
它内部维护着:
-
state
-
reducer
-
-
核心方法:
-
getState()
-
dispatch(action)
-
subscribe(listener)
-
-
具体编码:
-
store.getState()
-
store.dispatch({type:‘INCREMENT’, number})
-
store.subscribe(render)
-
-
applyMiddleware()
作用:应用上基于redux的中间件(插件库)
-
combineReducers()
作用:合并多个reducer函数
14. react-redux
一个react的插件库,专门用于简化在react应用中使用redux
react-redux将所有组件分为两大类:
-
UI组件
只负责UI呈现
不使用redux的API
通过props接收数据
-
容器组件
负责管理数据和业务逻辑
使用redux的API
基本使用:
-
如何创建一个容器组件
靠react-redux的connect函数
mapStateToProps 返回store中的state对象传递给UI组件
mapDispatchToProps 返回操作状态的方法对象dispatch传递给UI组件调用
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
// 引入Count的UI组件 import CountUI from "../../components/Count"; // 引入connect用于连接UI组件和redux import { connect } from "react-redux"; import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from "../../redux/count_action"; // a函数的返回值作为状态props传递给CountUI组件 state ———— redux保存的状态值 function mapStateToProps(state){ return {count: state}; } function mapDispatchToProps(dispatch){ return { increment: (data)=>{ dispatch(createIncrementAction(data)); }, decrement:(data)=>{ dispatch(createDecrementAction(data)); }, asyncIncrement: (data, time)=>{ dispatch(createIncrementAsyncAction(data, time)); }} } const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI); export default CountContainer;
-
容器组件中的store是靠props传递进去的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import React, { Component } from 'react' // import Count from './components/Count' import CountContainer from './containers/Count' import store from './redux/store' export default class App extends Component { render() { return ( <div> <CountContainer store={store}></CountContainer> </div> ) } }
-
Provider批量传递store
当需要给多个容器组件传递store时,在外部套一个
<Provider>
标签,给它传递一个store属性即可对所有容器组件进行传递1 2
const root = createRoot(document.getElementById('root')); root.render(<Provider store={store}><App></App></Provider>);
-
容器组件和UI组件可以整合成一个文件
-
combineReducer合并多个reducer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 引入createStore,专门用于创建redux中最为核心的store对象 import { createStore, applyMiddleware, combineReducers } from "redux" // 引入为Count组件服务的reducer import countReducer from './reducers/count' import personReducer from "./reducers/person"; import thunk from "redux-thunk" // 保存的对象键值就是以后需要用到的状态对象名 const allReducer = combineReducers( { count: countReducer, persons: personReducer } ); const store = createStore(allReducer, applyMiddleware(thunk)); // 暴露一个全局唯一store对象 export default store
-
redux-devtools-extension
redux开发者工具
1 2
import {composeWithDevTools} from 'redux-devtools-extension' const store = createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
15. React扩展
15.1. setState
setState更新状态两种方法:
|
|
15.2. lazyLoad
路由组件的lazyLoad
- 路由组件利用lazy函数动态加载
|
|
- 通过
<Suspense>
标签自定义一个Loading界面
|
|
15.2. Hooks
React Hooks可以让函数式组件使用state以及其他React特性
- 三个常用Hook
|
|
- State Hook
|
|
-
Effect Hook
Effect Hooks用于模拟类式组件中生命周期钩子
|
|
- Ref Hook
|
|
15.3. Fragment
用于忽略节点,最终不会在html中渲染
可以不用必须有一个真实的DOM根标签了
|
|
15.4. Context
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信(父子组件间props即可)
|
|
|
|
15.5. 组件优化
Component的2个问题
-
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
-
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
** 注意: **
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
class Demo extends PurComponent{
render(){
return (...)
}
}
15.6. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
15.7. Error boundary
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
|
|
15.8. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
16. ReactRouter 6 使用
与ReactRouter5相比,改变了:
- 内置组件变化,移除了
<Switch/>
,新增了<Routes/>
;移除了<Redirect>
,新增<Navigate>
- 语法变化:
component={About}
变为element={<About/>}
- 新增多个hook:
useParams
、useNavigate
、useMatch
、useLocation
等 - 官方明确推荐函数式组件
16.1. NavLink使用以及重定向
|
|
16.2. 路由表
- 建立一个单独文件用于存储路由表
|
|
- 使用useRoutes创建路由表
|
|
16.3. 嵌套路由
路由表
|
|
二级组件展示
|
|
16.4. 路由传参
Link指定路由路径传参
|
|
路由组件接收参数
|
|
16.5. 编程式路由导航
利用useNavigate实现路由跳转
|
|
一般组件内实现路由前进后退
|
|
16.6. useInRouterContext()
返回一个bool值,判断当前组件是否处于路由环境中
|
|
16.7. useNavigationType()
返回当前导航类型(用户是如何来到当前页面的)
返回值:POP
、PUSH
、REPLACE
16.8. useOutlet()
用来呈现当前组件中渲染的嵌套路由
|
|