文章目录
- react笔记
- react脚手架
- react安装步骤
- nrm的使用说明--拓展--可用可不用
- react文件分析
- react文件的引用顺序
- 样式的模块化--053
- react插件安装--054
- 父传子 子传父
- 安装uuid--058
- 安装PropTypes库--061
- 安装axios
- react脚手架配置代理总结
- 兄弟传值--消息订阅与发布模式
- react-router-dom路由
- 路由组件与一般组件
- NavLink的使用
- Switch的使用
- 样式丢失的问题-082
- 路由的严格匹配与模糊匹配-exact--084
- 路由组件传递参数-声明式导航
- push和replace两种模式
- 路由组件传递参数-编程式导航
- 在一般组件中使用history
- BrowserRouter与HashRouter的区别
- antd组件库的基本使用
- redux-状态管理的js库
- 打包react项目
- react拓展
react笔记
react的特点
- 采用组件化模式、声明式编码,提高开大效率及组件复用率。
- 在react native 中可以使用react语法进行移动端开发。
- 使用虚拟DOM+优秀的diffing算法,尽量减少与真实DOM的交互
react基础知识
react的引入
把依赖包放入文件中
在页面按照以下顺序进行引入
<!-- 引入react核心库 注意:引入的顺序不能变 --> <script type="text/javascript" src="../js/react.development.js"></script> <!-- 引入react-dom 用于支持react操作DOM --> <script type="text/javascript" src="../js/react-dom.development.js"></script> <!-- 引入babel 用于将jsx转化为js --> <script type="text/javascript" src="../js/babel.min.js"></script>
如果使用的jsx进行书写,则需要使用
<script type="text/babel">
jsx语法规则-005
jsx语法规则:
- 定义虚拟DOM时,不要写引号
- 标签中混入js表达式时要用{}
- 样式的类名指定不要用class,要用className className=“title”
- 内联样式,要用style={{ color: 'yellow ', fontSize: ‘20px’ }}方式写
- 注意:例如color可以直接写,也可以写成"color":“yellow”
- jsx不能有多个根标签,只能有一个
- 标签必须闭合
- 标签首字母
- 若小写字母开头,则将标签转化为html中同名元素,若html中无该标签对应的同名元素,则报错。
- 若大写字母开头,react就去渲染对应的组件,组件没有定义,则报错。
<script type="text/babel"> const myId = "aTgUIgu" const myData = "heLlO,rEaCt" // 1 创建虚拟DOM const VDom = ( <div> <h2 className="title" id="{myId.toLowerCase()}"> <span style={{ color: 'yellow ', fontSize: '20px' }}>{myData.toLowerCase()}</span> </h2> <input type="text" name="" id="" /> <good>789</good> </div> ) // 2 渲染虚拟DOM到页面 ReactDOM.render(VDom, document.getElementById('test')) </script>
区分js语句和js表达式-006
一定注意区分:【js语句(代码)】与【js表达式】
- **表达式:**一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式
1. a
2. a+b
3. demo(1)
4. arr.map()
5. function test(){}
- 语句(代码): 下面这些都是语句(代码)
- if(){}
- for(){}
- switch(){}
// 模拟一些数据 const data = ["angular", "react", "vue"] // 1. 创建虚拟DOM const Dom = ( <div> <h1>前端js框架列表</h1> <ul> { data.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> </div> ) // 2. 渲染虚拟DOM到页面 ReactDOM.render(Dom, document.getElementById('test'))
react中定义组件
函数式组件
// 1. 创建函数式组件 function Demo() { console.log(this)// 此处的this是unfinded,因为babel编译后开启了严格模式 return <h2>你好</h2> } // 2. 渲染组件到页面 注意:组件的首字母要大写 ReactDOM.render(<Demo />, document.getElementById('test')) /* 执行了ReactDOM.render(<Demo />)之后发生了什么? 1. react解析组件标签,找到了Demo组件 2. 发现组件时使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中 */
类式组件
// 1. 创建类式组件 class Demo extends React.Component { render() { // render是放在哪里的?--Demo的原型对象上,供实例使用。 // render中的this是谁?--Demo的实例对象。<==>Demo组件实例对象 console.log('this', this) return <h2>我是用类定义的组件(适用于复杂组件)的定义</h2> } } // 2. 渲染组件到左面上 ReactDOM.render(<Demo />, document.getElementById('test')) /* 执行了ReactDOM.render(<Demo />)之后发生了什么? 1. react解析组件标签,找到了Demo组件 2. 发现组件时使用函数定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。 3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。 */
函数式组件和类式组件的区别
https://segmentfault.com/a/1190000020861150
- 类式组件:
- 使用的时候,如果在里面定义函数的时候,不写成箭头函数的形式,则里面的this是undefined,因此在定义函数的时候当不需要this就可以使用普通函数,当需要this的时候使用箭头函数。
- 函数式组件:
- 函数式组件没有生命周期以及state,想要在函数式组件中使用state状态管理,通过const【num , setnum】=useState(num初始值),
- 函数式组件如果想要使用生命周期,那就需要在**
useEffect
**里面去使用。- 获取父组件传递过来的信息只需要和正常函数一样接收就可以了,例如function sub(props){},这里面的props就包含传递过来的信息。
对标签属性进行类型限制以及默认属性
// 这是限制输入的数据的类型 // 上面的p小写,下面的p大写 Person.propTypes={ name:PropTypes.string.isRequired, // isRequired表示必填 sex:PropTypes.string, age:PropType.number, speak:PropType.func } // 指定默认标签属性 Person.defaultProps={ sex:'男' }
类型的限制和默认值一般都是在子组件中进行限制。
关于ref的使用
ref用来获取对应的组件。通过this可以看到当前组件中的都有哪些属性:
ref使用有两种方法:
方法一:在组件或者输入框中直接使用。例如:
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
,然后在调用的时候通过this.refs.input1
就可以使用了;方法二:使用回调的形式使用。例如:
<input ref={(currentNode) => { this.input1 = currentNode }} type="text" placeholder="点击按钮提示数据" />
,然后再调用的时候通过this.input1
就就可以使用了;回调ref的两种写法以及调用次数:写在ref里面的都只会在刚加载页面的时候执行一次,把这个节点挂载this指向的函数
写法一:直接写在标签内,更新时会调用两次,第一次调用是在刚进入页面的时候,会自动调用一次,把这个节点挂载在this.input1上。 <input ref={(currentNode) => {this.input1 = currentNode}} type="text" /> 写法二:是写了一个箭头函数,在箭头函数中通过`this.input1 = currentNode`设置,然后就可以在其他函数中使用this.input了,这个函数只在加载的时候执行一次 <input ref={this.saveInput} type="text"/> saveInput = (currentNode) => { this.input1 = currentNode }
关于**React.createRef()**的使用:
使用方法:调用的时候通过:
this.myRef.current.value
获取里面的值myRef = React.createRef() <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" /> 调用的时候通过:this.myRef.current.value获取里面的值
react中事件处理
通过 onXxx 属性指定事件处理函数(注意大小写) ,例如onClick–onBlur
1) React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件—为了很好的兼容性
2) React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)—为了高效
通过 event.target 得到发生事件的 DOM 元素对象—不要过度使用ref
受控组件与非受控组件
非受控组件:现用现取,例如输入框,输入后直接使用。外部状态改变只有第一次才会收到影响,之后不会收到影响
受控组件:状态存储到state中,用的时候从state中获取。外部状态改变能够影响到内部组件
受控组件可以不用写ref
e.preventDefault()// 阻止默认事件
onChange是可以获取到输入框输入的数据,方法是用过
e.target.value
来使用,获取组件信息通过e.target
- 非受控组件: 用户输入A => input 中显示A
- 受控组件: 用户输入A => 触发onChange事件 => handleChange 中设置 state.name = “A” => 渲染input使他的value变成A
高阶函数-函数柯里化
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数;
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数;
- 常见的高阶函数:promise setTimeout
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
<input onChange={this.saveFormData('username')} type="text" name="username" /> // 保存表单数据到状态中 saveFormData = (e) => { console.log('1', e) // 数据是函数传过来的username return (event) => { console.log('2', e, event.target.value) //event.target.value是输入框输入的数据 this.setState({ [e]: event.target.value }) } }
不用柯里化实现:
<input onChange={(event) => { this.saveFormData('username', event) }} type="text" name="username" /> // 保存表单数据到状态中 saveFormData = (data, event) => { console.log('1', data, event) this.setState({ [data]: event.target.value }) }
组件的生命周期
旧生命周期
生命周期的三个阶段(旧)
初始化阶段: 由 ReactDOM.render()触发—初次渲染
- constructor() — 构造器
- componentWillMount() ---- 组件将要挂载的钩子
- render()
- componentDidMount() 组件挂载完毕后的钩子,只会在最开始的时候调用一次===>常用, 一般在这个钩子做一些初始化的事。例如:开启定时器,发送网络请求、订阅消息
- 组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
- 此组件只会在调用组件的时候执行一次,数据更新这个组件不会被触发。
更新阶段: 由组件内部 this.setSate()或父组件重新 render 触发
- shouldComponentUpdate( nextProps,nextState ) {return true/false/null}---- 控制组件更新的阀门
- 主要用于性能优化(部分更新)
- 唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新;
因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断;- componentWillUpdate() —组件将要更新的钩子
- render()
- componentDidUpdate(prevProps,prevState) —组件更新完毕后的钩子
卸载组件: 由 ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() 组件将要卸载的钩子===> 常用,一般在这个钩子中做一些收尾的事,例如:关闭定时器,取消订阅消息
一般在这个钩子中做一些收尾的事,例如:关闭定时器,取消订阅消息
react脚手架
react安装步骤
解决下包太慢问题:
npm切换成国内的镜像服务来使用,淘宝镜像:
npm config set registry http://registry.npm.taobao.org/
查看当前的代理
npm get registry
全局安装:
npm i -g create-react-app
切换到想创项目的目录,使用命令:
create-react-app 项目名字
注意:在引入文件的时候,后缀是js和jsx的,引用的时候可以省略后缀
创建组件时:首字母大写
nrm的使用说明–拓展–可用可不用
nrm 是一个 npm 源管理器,允许你快速地在 npm源间切换。
的安装:
npm install -g nrm
查看当前可选择源:
nrm ls
查看当前源:
nrm current
切换源:
nrm use 名称
添加源:
nrm add 名称 地址
删除源:
nrm del 名称
测试源速度:
nrm test 名称
react文件分析
react文件的引用顺序
- public文件中index.html文件时用来显示在页面上的,显示在里面的div标签
- src文件中的index.js文件时引入App.js文件,让其显示在public的index.html文件中
- App.js是引入各个组件,让其显示在页面中
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <link rel="icon" href="/react_jiao/public/favicon.ico" /> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="root"></div> </body> </html>
src/index.js
// 引入react核心库 import React from 'react' // 引入ReactDOM import ReactDOM from 'react-dom/client' import App from './App' const root = ReactDOM.createRoot(document.getElementById('root')); root.render( // <React.StrictMode>检查代码不合理的地方 <React.StrictMode> <App /> </React.StrictMode> )
src/App.js
// 创建外壳组件App import React, { Component } from 'react' import Hello from './components/Hello' import Welcome from './components/Welcome' // ! 引入组件使用的时候,首字母大写 export default class App extends Component { render() { return ( <div> <Hello></Hello> <Welcome></Welcome> </div> ) } }
样式的模块化–053
如果直接使用样式,不进行处理,则相同的样式名称会被覆盖,因此需要样式的模块化,有两种方法:
方法一:在央视的外面再加一层,例如使用Hello组件演示:
.hello{ .title{ background-color:Red } }
import './index.css' export default class hello extends Component { render() { return <h2 className='title'>hello,组件</h2> } }
方法二: 把.css文件改为
.module.css
,引入文件时使用import 名字 from ‘地址’
import hello from './index.module.css' export default class Hello extends Component { render() { return <h2 className={hello.title}>hello,组件</h2> } }
react插件安装–054
插件名称: ES7+ React/Redux/React-Native snippets
快捷方式: VS Code React快速代码插件ES7+ React/Redux/React-Native snippets
父传子 子传父
父传子:
子传父可以直接传递,例如把父组件的state里面的数据传递给子组件,则只需要在子组件中写入即可
<List todo={this.state.todos}></List>
在子组件中使用是通过
this.props.todos
使用
子传父:
父组件通过向子组件传递函数,子组件修改函数的值即可实现子传父
父组件 a = (data) => { console.log('a', data) } <Header rr={this.a}></Header> 子组件 this.props.rr(e.target.value)
安装uuid–058
作用:生成一个不重复的数据
安装:
npm i nanoid
引用:
import { nanoid } from 'nanoid'
安装PropTypes库–061
PropTypes库:用于限制传递数据的类型
安装:
npm i prop-types
引用:
import PropTypes from 'prop-types'
使用的时候是用于判断传递过来的数据类型的,因此一般是在子组件中使用,从而判断父组件传递过来的数据是否符合规定类型。
app.js
<Header rr={this.a}></Header>
Header.js
import PropTypes from 'prop-types' // ! 对接收的props进行:类型、必要性的限制 static propTypes = { rr: PropTypes.func.isRequired }
安装axios
安装:
npm i axios
跨域问题:可以发送请求,但是数据返回时会被axios拦截。
react脚手架配置代理总结
方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
- 第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
- 编写setupProxy.js(名字是固定的)配置具体代理规则:
const { createProxyMiddleware: proxy } = require('http-proxy-middleware'); module.exports = function(app) { app.use( proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000) target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址) changeOrigin: true, //控制服务器接收到的请求头中host字段的值 /* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000 changeOrigin默认值为false,但我们一般将changeOrigin值设为true */ pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置) }), proxy('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
注意:如果代理出错,可能是代码的问题,解决问题方法: React创建配置代理文件setupProxy.js,启动项目无法访问
兄弟传值–消息订阅与发布模式
安装
npm i pubsub-js
在订阅和发布的文件中都引入
import PubSub from 'pubsub-js'
传出数据使用:
PubSub.publish('名字', 数据)
接收数据使用:
this.token = PubSub.subscribe('名字', (名字, data) => { console.log(data) })
在接收数据的时候,一般在组件挂载后componentDidMount
组件销毁后要取消订阅
componentWillUnmount(){ PubSub.unsubscribe(this.token) }
react-router-dom路由
- react的一个插件库
- 转么用来实现一个SPA应用
- 基于react的项目基本都会用到此库
安装路由:
npm i react-router-dom@5
(学习的是5版本)引入:
import {} from 'react-router-dom'
用法一:
- 在index.js中引入
import { BrowserRouter } from 'react-router-dom'
import React from 'react' import ReactDOM from 'react-dom/client' import { BrowserRouter } from 'react-router-dom' import App from './App' const root = ReactDOM.createRoot(document.getElementById('root')) root.render(<BrowserRouter> <App /> </BrowserRouter>)
- 在App.js中通过使用如下代码实现
点击跳转 <Link className='list-group-item' to="/about">About</Link> <Link className='list-group-item' to="/home">Home</Link> 显示对应页面 <Route path="/about" component={About}></Route> <Route path="/home" component={Home}></Route>
路由组件与一般组件
- 写法不同:
- 一般组件:
- 路由组件:
- 存放位置不同:
- 一般组件:component
- 路由组件:pages
- 接收到的props不同:
- 一般组件:写组件标签时传递了什么,就能收到什么
- 路由组件:接收到三个固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: “/about”
search: “”
state: undefined
match:
params: {}
path: “/about”
url: “/about”
NavLink的使用
**NavLink和Link的区别就是:**点击对应的按钮,可以实现高亮的效果,通过activeClassName指定对应的样式
<NavLink activeClassName='demo' className='list-group-item' to="/about">About</NavLink>
封装NavList:
创建一个MyNavList的组件,然后在里面把NavList封装进去,可以使用{…this.props}接收外面传递过来的属性,而且如果在外面标签写内容,也会通过this.props.children传递给组件
父组件: <MyNavLink to="/about" title="About">AboutAAA</MyNavLink> 子组件: <NavLink activeClassName='demo' className='list-group-item' {...this.props} />
Switch的使用
使用Switch可以使Route中如果出现相同路径,多个组件的,就会只显示第一个组件
import { Route, Switch } from 'react-router-dom'
<Switch> <Route path="/about" component={About}></Route> <Route path="/home" component={Home}></Route> <Route path="/home" component={Test}></Route> </Switch>
就会只显示Home,不显示Test。
样式丢失的问题-082
如果随意写路径,选中后再刷新,样式就可能会出现丢失的情况,解决方法有三种:
方法一:使用HashRouter(不常用)
方法二:在public中的index.html引入的样式文件不写 ./ 写后面的这个:
<link rel="stylesheet" href="/css/bootstrap.css">
方法三:在public中的index.html引入的样式文件不写./,使用%PUBLIC_URL%:
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
路由的严格匹配与模糊匹配-exact–084
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
路由组件传递参数-声明式导航
传递params参数–086
向路由组件传递params参数:
<Link to={`/home/message/detail/${mapObj.id}/${mapObj.title}`}>
声明接收params参数:
<Route path="/home/message/detail/:id/:title" component={Detail} >
在组件中通过
const { id, title } = this.props.match.params
拿到数据this.props.match.params
优点:刷新后也能保留住参数,参数直接在搜索栏中显示
缺点:只能传递字符串,并且参数过多的时候url会变得丑陋
传递search参数–087
安装querystring库:
npm install qs
引入使用:
import qs from 'qs'
向路由组件传递search参数:
<Link to={`/home/message/detail?id=${mapObj.id}&title=${mapObj.title}`}>{mapObj.title}</Link>
search无需声明接收
<Route path="/home/message/detail" component={Detail} ></Route>
在组件中,数据存储在this.props.location.search中,而且需要把数据利用qs进行处理。
const { id, title } = qs.parse(search.slice(1))
slice(1)截取第一个字符删除。
qs说明:qs.stringify()和qs.parse()
let obj = { name: ‘tom’, age: 18 }
console.log(qs.stringify(obj)) //name=tom&age=18
let str = ‘car=奔驰&pirce=199’
console.log(qs.parse(str)) // 数据转化为obj形式
刷新后也能保留住参数,参数直接在搜索栏中显示
传递state参数–088
向路由组件中传递state参数:
<Link to={{ pathname: '/home/message/detail', state: { id: mapObj.id, title: mapObj.title } }}>{mapObj.title}</Link>
state无需声明接收:
<Route path="/home/message/detail" component={Detail} ></Route>
在组件中,数据存储在this.props.location.state中:
const { id, title } = this.props.location.state
刷新后也能保留住参数
push和replace两种模式
push是默认的模式,点击回退可以退回到上一个操作的页面
replace是替代模式,只需要在对应的跳转标签中加入replace即可,就会替换当前的页面,点击回退不能退回到被替换的页面
<Link replace to={`/home/message/detail`}>{mapObj.title}</Link>
路由组件传递参数-编程式导航
例如点击按钮执行函数进行跳转,在函数体内书写下面代码,其他的和声明式导航一样:
// 编程式导航--params this.props.history.replace(`/home/message/detail/${id}/${title}`) 或者this.props.history.push(`/home/message/detail/${id}/${title}`) // 编程式导航--search this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`) // 编程式导航--state this.props.history.replace(`/home/message/detai`,{id:id,title:title})
编程式还可以使用如下api
this..props.go(n) this..props.goBack() 回退 this..props.goForward() 前进
在一般组件中使用history
路由组件可以在不传递数据的情况下在组件中获取this.props,而一般组件则不可以。因此在一般组件中不能使用this.porps.history
解决方法:withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
在一般组件中这样写引入
import { withRouter } from 'react-router-dom'
,导出的时候使用export default withRouter(Header)
import React, { Component } from 'react' import { withRouter } from 'react-router-dom' class Header extends Component { } export default withRouter(Header)
BrowserRouter与HashRouter的区别
- 底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
- HashRouter使用的是URL的哈希值。
- path表现形式不一样
- BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
- HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响
- BrowserRouter没有任何影响,因为state保存在history对象中。
- HashRouter刷新后会导致路由state参数的丢失!!!
- 备注:HashRouter可以用于解决一些路径错误相关的问题。
antd组件库的基本使用
安装:
npm i antd
如果使用的是按钮图标的话,使用方法:通过
import { Button } from 'antd';
引入,然后就可以直接使用;如果使用icon图标的话需要安装
npm install --save @ant-design/icons
,然后引入对应的图标就可以使用:import { SyncOutlined, } from '@ant-design/icons'
样式的按需引入—095
https://ant.design/docs/react/use-with-create-react-app-cn
redux-状态管理的js库
redux是什么
- redux 是一个专门用于做状态管理的 JS 库(不是 react 插件库)。
- 它可以用在 react, angular, vue 等项目中, 但基本与 react 配合使用。
- 作用: 集中式管理 react 应用中多个组件共享的状态。
什么情况使用:
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
redux工作流程图
redux的使用
安装:
npm i redux
redux的简单使用
- 创建redux文件,在里面创建store.js和count_reducer.js
- 在store.js中写上如下代码,用于调用以及传递信息。
- 引入redux中的createStore函数,创建一个store;
- createStore调用时要传入一个为其服务的reducer;
- 记得暴露store对象;
// 引入createStore,专门用于创建redux中最核心的store对象 import { legacy_createStore as createStore } from 'redux' // 引入Count组件服务的reducer import countReducer from './count_reducer' export default createStore(countReducer)
在count_reducer.js中写入如下代码,用于处理数据:
reducer的本质是一个函数,接收:preState,action,返回加工后的状态
;reducer有两个作用:初始化状态,加工状态;
reducer被第一次调用时,是store自动触发的,
- 传递的preState是undefined;在后续的执行中,这个是用来保存上依稀的数据的。
- 传递的action是:{type:'@@REDUX/INIT_a.2.b.4},第一次这样传递是为了避免重复。
/* 1. 该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数 2. reducer函数会接到两个参数,分别是:之前的状态(preState),动作对象(action) */ /* const initState = 0 // 初始化状态 export default function countReducer(preState=initState, action) { */ export default function countReducer(preState, action) { console.log(preState, action) if (preState === undefined) preState = 0 const { type, data } = action // 根据type决定如何加工数据 switch (type) { case 'increment'://加 return preState + data case 'decrement':// 减 return preState - data default: // 初始化的时候 return preState } }
在组件中通过
<h1>当前求和为:{store.getState()}</h1>
中*store.getState()可以获取到count_reducer.js中的返回信息。通过
store.dispatch({ type: "increment", data: value * 1 })
通过**store.dispatch()**可以向store传递数据,从而给count_reducer.js因为redux在数据更新的时候不会更新render,因此需要store.subscribe()进行更新:
方法一:在当前组件中添加这个 componentDidMount() { // 检测redux中状态发生变化,只要变化,就会调用 store.subscribe(() => { this.setState({}) }) }
方法二:在最外层的index.js中添加 import store from './redux/store' const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />) store.subscribe(() => { root.render(<App />) })
redux完整版
新增文件:
count_action.js 专门用于创建action对象。
/** * 该文件专门为Count组价生成action对象 */ import { INCREMENT, DECREMENT } from './constant' /* const createIncrementAction = (data) => { return { type: 'increment', data } } */ // 使用这种方式写,返回的是一个对象,和上面的作用是一样的 // export const createIncrementAction = data => ({ type: 'increment', data }) export const createIncrementAction = data => ({ type: INCREMENT, data }) export const createDecrementAction = data => ({ type: DECREMENT, data })
constant.js 放置容易写错action中的type。
/** * 该模块使用用于定义action对象中type类型的常量值 * 目的只有一个:便于管理的同时防止程序员写错单词 */ export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
同步异步版action
安装插件:
npm install redux-thunk --save
在store.js中写入如下代码:
import thunk from 'redux-thunk'
applyMiddleware
// 引入createStore,专门用于创建redux中最核心的store对象 import { legacy_createStore as createStore, applyMiddleware } from 'redux' // 引入Count组件服务的reducer import countReducer from './count_reducer' // 引入redux-thunk,用于支持异步action,与applyMiddleware()配合使用 import thunk from 'redux-thunk' export default createStore(countReducer, applyMiddleware(thunk))
然后就可以在count_action.js中使用返回值为函数的代码了。
export const createIncrementAsyncAction = (data, time) => { return () => { setTimeout(() => { store.dispatch(createIncrementAction(data * 1)) }, time); } }
在action中有同步和异步之分:
- 同步action,就是指action的值为Object类型的一般对象
- 异步action,就是指action的值为函数,需要在store.js中引入redux-thunk插件
- 明确:延迟的动作不想交给组件自身,想交给action
- 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
- 具体编码:
nopm i redux-thunk
,并配置在store中- 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
- 异步任务有结果后,分发一个同步的action去真正操作数据。
- 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
react-redux的使用–104
安装组件:
npm i react-redux -S
因为react-redux的结构逻辑:
因此我们需要创建一个Count的容器,可以创建一个文件夹用于存放不同的容器,命名为containders,然后在里面创建对应的容器组件文件夹。
引入方式:在Count容器组件中通过引入UI组件,使用react-redux让容器和UI组件产生关联
// 容器组件 // 引入Count的UI组件 import CountUI from '../../components/Count' // 引入connect用于连接UI组件与redux import { connect } from 'react-redux' export default connect()(CountUI)
然后把App.jsx中的Count组件改为容器组件,在<Count中引入store>
import React, { Component } from 'react' import Count from './containers/Count' import store from './redux/store' export default class App extends Component { render() { return ( <div> <Count store={store}></Count> </div> ) } }
接下来就是详细的细节展示:
在容器组件中可以使用如下代码来接收通过App.jsx传递过来的store
容器组件通过
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
把数据暴露出去
- 其中mapStateToProps函数是用来传递状态的,也就相当于把state传递给UI组件,因此传递的是对象数据类型
- mapDispatchToProps是用来传递操作状态的,就相当把加减等传递过去,因此传递的是函数的数据类型
// 容器组件 // 引入Count的UI组件 import CountUI from '../../components/Count' // 引入connect用于连接UI组件与redux import { connect } from 'react-redux' // import { INCREMENT } from '../../redux/constant' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action' // ? 在App.jsx中传入store,因此只需要在mapStateToProps函数中接收数据即可,不用再通过使用store.dispatch()进行接收 /** * 1. mapStateToProps函数返回的是一个对象;并且函数名字可以随意书写 * 2. 返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件的props的value * ? 3. mapStateToProps用于传递状态 */ function mapStateToProps(state) { return { count: state } } /** * 1. mapDispatchToProps函数返回的是一个对象;并且函数名字可以随意书写 * 2. 返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件的props的value * ? 3. mapDispatchToProps用于传递操作状态的方法 */ function mapDispatchToProps(dispatch) { return { jia: (data) => { // 通知redux执行 // dispatch({ type: INCREMENT, data: data }) dispatch(createIncrementAction(data)) }, jian: data => dispatch(createDecrementAction(data)), jiaSync: (data, time) => dispatch(createIncrementAsyncAction(data, time)) } } // ? a和b是随便写,但是作用是固定的 // export default connect(a, b)(CountUI) /** * mapStateToProps是用来传递状态的 * mapDispatchToProps是用来传递操作状态的 * (CountUI)是用来表示和哪个UI组件进行关联的 */ export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
数据传递过去给UI组件后,通过this.props.**()就可以使用了。下面截图显示的是容器组件传递过来的数据
react-redux的优化–容器代码的优化
代码方面的优化:
优化前:
const mapStateToProps = (state) => { return { count: state } } const mapDispatchToProps = (dispatch) => ( { jia: (data) => { dispatch(createIncrementAction(data)) }, jian: data => dispatch(createDecrementAction(data)), jiaSync: (data, time) => dispatch(createIncrementAsyncAction(data, time)) } ) export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
代码优化:
export default connect(state => ({ count: state }), (dispatch) => ({ jia: (data) => { dispatch(createIncrementAction(data)) }, jian: data => dispatch(createDecrementAction(data)), jiaSync: (data, time) => dispatch(createIncrementAsyncAction(data, time)) }) )(CountUI)
API级别的优化—这个是react-redux优化的:
export default connect(state => ({ count: state }), // ! api级别的精简写法,这是因为react-redux处理的 { jia: createIncrementAction, jian: createDecrementAction, jiaSync: createIncrementAsyncAction } )(CountUI)
注:
- 容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
- mapDispatchToProps,也可以是一个对象
使用react-redux后就不需要在最外层的index.js文件夹中使用store中的
store.subscribe(()=>{root.render()})
去监控数据改变从而调用render如果在App.js文件中使用
<Count store={store}></Count>
方式,当容器组件很多的时候就需要在每个组件中都引入store,这样写很麻烦。优化方法如下:
在最外层的index.js文件中使用Provider。这样就不用一个一个的写store了。
import store from './redux/store' import { Provider } from 'react-redux' const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> )
文件优化:由于UI组件和容器组件写在两个文件里面,会增加文件的数量,因此可以把UI组件放到容器组件里面,然后把容器组件export default出来,UI组件就不需要export了,这样就会简化文件夹。
- 容器组件和UI组件整合一个文件
- 无需自己给容器组件传递store,给包裹一个即可。
- 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
- mapDispatchToProps也可以简单的写成一个对象
- 一个组件要和redux“打交道”要经过哪几步?
- 定义好UI组件—不暴露
- 引入connect生成一个容器组件,并暴露,写法如下:
connect(
state => ({key:value}), //映射状态
{key:xxxxxAction} //映射操作状态的方法
)(UI组件)- 在UI组件中通过this.props.xxxxxxx读取和操作状态
react-redux数据共享版
为了使不同容器之间也能进行数据共享,因此就需要在store.js中引入combineReducers,使用方法:
import { legacy_createStore as createStore, applyMiddleware, combineReducers } from 'redux' // 汇总 const allReducer = combineReducers({ he: countReducer, rens: personReducer, }) export default createStore(allReducer, applyMiddleware(thunk))
这样就可以在一个store中引入多个容器组件。
接下来只需要在容器组件中引入对应的数据即可,例如:
// 容器组件 在state中引入 export default connect(state => ({ count: state.he, renshu: state.rens.length }), { jia: createIncrementAction, jian: createDecrementAction, jiaSync: createIncrementAsyncAction } )(Count)
引入后在UI组件中通过this,props.renshu就可以直接使用。
纯函数–112
纯函数:一个函数的返回结果只依赖传给函数的参数,并且执行过程中没有副作用。
- 执行中没有副作用的意思是指和传入的参数有关,而且参数传入函数中没有被修改。
// 非纯函数 返回值与a相关,无法预料 const a = 1 const foo = (b) => a + b foo(2) // => 3 // 纯函数 返回结果只依赖于它的参数 x 和 b,foo(1, 2) 永远是 3。传入的参数是确定的,那么 foo(1, 2) 的值永远是可预料的。 const a = 1 const foo = (x, b) => x + b foo(1, 2) // => 3 /************************************************************************/ // 无副作用 const a = 1 const foo = (obj, b) => { return obj.x + b } const counter = { x: 1 } foo(counter, 2) // => 3 counter.x // => 1 // 修改一下 ,再观察(修改了外部变量,产生了副作用。) const a = 1 const foo = (obj, b) => { // 数据在这里产生了修改,也就产生了副作用 obj.x = 2; return obj.x + b } const counter = { x: 1 } foo(counter, 2) // => 4 counter.x // => 2
必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用 Date.now()或者 Math.random()等不纯的方法
redux 的 reducer 函数必须是一个纯函数
关于redux插件
浏览器安装redux插件
在vscode中安装组件库:
npm i redux-devtools-extension
在store.js文件中加入这个组件库的composeWithDevTools方法,使用方法如下:
// 引入 redux-devtools-extension import { composeWithDevTools } from 'redux-devtools-extension' export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
打包react项目
打包:
npm run build
全局安装:
npm i serve -g
启动服务器:
serve build
react拓展
react的知识拓展
setState的两种写法
setState的相关知识:
- setState是一个异步的API,因此在执行的会先执同步的函数,执行完成后才回执行异步函数。
- 但是当setState出现在自定义的dom事件以及在setTimeout中时,它是同步执行的。
写法一: const { count } = this.state // 对象式的setState this.setState({ count: count + 1 }, () => { // 能够显示最新的值 console.log('count回调', this.state.count) }) console.log('count', this.state.count) 写法二: // 函数式的setState this.setState((state, props) => { console.log('函数式', state, props) return { count: state.count + 1 } }, () => { })
总结:
- 对象式的setState是函数式的setState的简写方式(语法糖)
- 使用原则:
- 如果新状态不依赖于原状态 => 使用对象方式
- 如果新状态依赖于原状态=> 使用函数方式
- 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读
路由懒加载
安装路由组件:
npm i react-rouer-dom
在lazyLoad文件中的index.js组件通过这用方式引入对应的组件:
const Home = lazy(() => import('./Home'))
引入react中的这几个函数:
import React, { Component, lazy, Suspense } from 'react'
,其中lazy在引入组件的时候使用,Suspense是在路由跳转的使用使用:<Suspense fallback={<Loading />}> {/* 注册路由 */} <Route path="/about" component={About} /> <Route path="/home" component={Home} /> </Suspense>
其中**fallback={}**是用来在加载组件的时候等待的界面,等待的界面是直接加载,不能使用懒加载。
Hook
组件分为类式组件和函数式组件,Hook是函数式组件中使用的。
Hook-useState
Hook使用是在函数式组件中使用,因为函数组件中this的定义是undefined,因此需要使用Hook。
State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作。
语法: const [xxx, setXxx] = React.useState(initValue)
useState()说明:
- 参数: 第一次初始化指定的值在内部作缓存。
- 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数。
setXxx()的2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值;
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值;
实例如下:
function Demo() { /* 【a, b】中第一个数据就是传入的0,第二个数据是对数据进行处理的。 const [a, b] = React.useState(0) console.log(a, b) */ // ? 注意:React.useState(0)中的0只是一个初始的数据,可以写其他任何数据,在后面计算中会自动把count的值赋给它, const [count, setCount] = React.useState(0) const [name, setName] = React.useState("tom") function add() { console.log(this) console.log('点击加号') // setCount(count + 1) // 第一种写法 // setCount(count => { return count + 1 }) // 第二种写法 // 第二种写法简化版 setCount(count => count + 1) setName('jack') } return ( <div> <h2>当前求和为:{count}</h2> <h2>wode名字是:{name}</h2> <button onClick={add}>点我+1</button> </div> ) } export default Demo
useEffect Hook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM语法和说明:
useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 } }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
useEffect(()=>{},[])是可以传递两个参数的:
- 第一个参数是当数据发生变化的时候会触发里面的函数
- 第二个参数是监控填入进去的数据,如果一个数据都不填写,则监控所有的useState数据,如果填写一个或多个,则只监控填写进去的数据的变化,其他的不监控
Ref Hook
- Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
- 语法: const refContainer = useRef()
- 作用:保存标签对象,功能与React.createRef()一样
Fragment–用于替代组件最外层的div
这两种写法都可以取代最外层的div
<Fragment><Fragment> <></>
Context–祖孙通信
在A组件的最外层(A的外层)使用如下代码:目的是引入Context。
const MyContext = React.createContext() const { Provider, Consumer } = MyContext
然后在A中引入B组件的时候包裹如下代码:
<Provider value={{ ...this.state }}> {/* <Provider value={this.state.username}> */} <B></B> </Provider>
在C组件中,如果是类组件,就通过
static contextType = MyContext
代码引入以及使用:class C extends Component { static contextType = MyContext // 这句话是用来接收的 render() { console.log(this.context) return ( < div > <h3>我是C组件</h3> <h4>我从A组件获取的用户名是:{this.context}</h4> </ div> ) } }
如果是函数组件的话,需要通过
<Consumer>
进行使用,代码如下:function C() { return ( < div > <h3>我是C组件</h3> <h4>我从A组件获取的用户名是: <Consumer> { value => { return `${value.username},年龄是${value.age}` } } </Consumer> </h4> </ div> ) }
PureComponent的使用
import React, { PureComponent } from 'react'
export default class A extends PureComponent { render{ return( ) } }
开发中会使用PureComponent 进行比较,这样就会当对应组件组件的数据没有更新的时候,就不会重新渲染数据没有改变的组件。#
renderProps–类似于Vue的插槽
https://blog.csdn.net/m0_56946322/article/details/117753276
当多个组件在一个组价中使用的时候这样的结构,当B需要调用A组件中的内容就会很不方便,因此使用这样的方式编写:
<A render={(data) => <C data={data}></C>}></A> A组件: {this.props.render(内部state数据)} C组件: 读取A组件传入的数据显示 {this.props.data}
在父组件中这样调用两个组件 <A render={(name) => <B name={name} />} /> 如果B需要A组件的数据只需要在A组件中使用{this.props.render()}即可,相当于vue的插槽技术 父组件: render() { return ( <div> <h3>我是RenderProps</h3> {/* <A><B /></A> */} /* 使用 */ <A render={(name) => <B name={name} />} /> </div> ) } } class A extends Component { state = { name: '小小' } render() { console.log(this.props) const { name } = this.state return ( <div> <h3>我是A组件</h3> /* 使用 */ {this.props.render(name)} </div> ) } } class B extends Component { render() { return ( <div> <h3>我是B组件,{this.props.name}</h3> </div> ) } }
新的思路逻辑:
在RenderProps里面通过调用
<A> <B /> </A>
,一般的方法是在RenderProps里面调用A,然后再在A的里面通过使用{this.props.children}调用B,这样AB都可以显示在页面上了。使用RenderProps的方式书写要相对简单一些:
在最外层组件中通过
<A render={(data) => <B data={data} />} />
其中render中的data数据是在A组件汇总传递出去的,传递方式是通过一下方式进行传递:class A extends Component{ state={ name:'小小', age:'18' } render(){ const {name, age} = this.state return { <div> <h3>我是A组件</h3> { // 传递多个属性的时候记得加 { } this.props.render({name,age}) // 如果值传递一个属性的时候不用加 { } // this.props.render(name) } } </div> } } }
通过this.props.render({name,age})就把数据传递给了
<A render={(data) => <B data={data} />} />
中的data,然后通过箭头函数显示出B并把输入传入到B中,B只需要按照正常的方式接收数据即可。B接收的时候使用
this.props.名字
,因为传递给B的时候使用的是data,因此在接收的时候使用this.props.data。
错误边界
错误边界:用于捕获后代组件错误,渲染出备用页面;
特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误。
当父组件的子组件在报错的时候就会触发这个函数,并且携带错误信息
static getDerivedStateFromError(err){}
组件渲染出错会调用这个函数
componentDidCatch(){}
,用于统计错误,然后反馈给服务器。使用:
// 生命周期函数,一旦后台组件报错,就会触发 // 当父组件的子组件报错的时候,会触发这个函数,并携带错误信息 static getDerivedStateFromError(err) { console.log('err', err) return { hasError: err } } // 组件渲染时出错会调用这个函数,用于统计错误,反馈给服务器 componentDidCatch() { console.log('渲染错误,反馈给服务器,通知编码人员') } render() { return ( <div> <h3>父组件</h3> {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child></Child>} </div> ) }
React-router6相关知识
路由的安装
安装:
npm i react-router-dom
关于Routes
router6使用Routes代替Switch,以及引入路径的改变。使用element代替component:
重定向使用:Navigate
// react-router5的写法 import { Route, Switch } from 'react-router-dom' <Switch> <Route path="/about" component={About}></Route> <Route path="/home" component={Home}></Route> <Route path="/home" component={Test}></Route> </Switch> // react-router6的写法 import { Route, Routes, Navigate } from 'react-router-dom' <Routes> <Route path="/about" element={<About />}></Route> <Route path="/home" element={<Home />}></Route> // 重定向 <Route path="/home" element={<Navigate to="/about" />}></Route> </Routes>
useRoutes路由表的使用
使用useRoutes:
import { Navigate, useRoutes } from 'react-router-dom' const ele = useRoutes( [ { path: '/about', element: <About /> }, { path: '/home', element: <Home />, chidlren:[ { // 注意,二级路由children里面的路径不用写 / path:'news', element:<News /> } ] }, { path: '/', element: <Navigate to='/about' /> }, ] ) // 然后在下面直接使用{ele}就可以了 <div className="panel-body"> {ele} </div>
一般情况下路由表和页面不在同一个文件夹中,然后我们就可以使用如下方式:
// 当路由表和使用它的文件不在一个页面的时候 // 路由表文件 export default [ { path: '/about', element: <About /> }, { path: '/home', element: <Home /> }, { path: '/', element: <Navigate to='/about' /> }, ] // 在引入它的文件中 import { useRoutes } from 'react-router-dom' //然后再下面使用,其中router是引用的上面的文件的文件名 const ele = useRouters(router) // 然后在render中使用 {ele}
react-router6路由传参
params传参
传递参数:
<Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link>
路由设置:
{ // params传参 path: 'detail/:id/:title/:content', element: <Detail /> }
接收参数:
import { useParams } from 'react-router-dom'
//? useParams是router6中用来获取params传递过来的数据的 import { useParams } from 'react-router-dom' const { id, title, content } = useParams()
search传参
传递参数:
<Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`}>{m.title}</Link>
路由不用设置。
接收参数:
import { useSearchParams } from 'react-router-dom'
import { useSearchParams } from 'react-router-dom' const [search, setSearch] = useSearchParams() const id = search.get('id') const title = search.get('title') const content = search.get('content') // 还可以通过下面这个方法更改数据 <button onClick={() => setSearch('id=008&title=哈哈&content=ccc')}>点我更新</button>
state传参
传递参数:
<Link to="detail" state={{ id: m.id, title: m.title, content: m.content }}>{m.title}</Link>
路由不用设置。
接收参数:
import { useLocation } from 'react-router-dom'
import { useLocation } from 'react-router-dom' // 连续结构赋值 const { state: { id, title, content } } = useLocation()
react-router6编程式导航–函数式组件
编程式导航需要用到useNavigate :
import {useNavigate } from 'react-router-dom'
<button onClick={() => showDetail(m)}>点击跳转</button> function showDetail(m) { console.log('m', m) // ? 编程式导航 navigate('detail', { replace: false, // 注意:这里只能写state state: { id: m.id, title: m.title, content: m.content } }) }
前进后退:
import { useNavigate } from 'react-router-dom' <button onClick={forward}>前进</button> const navigate = useNavigate() function forward() { navigate(1) }
零碎知识
单选框中的:defaultChecked只在第一次起作用
要想把state的数据都传给子组件使用:
<List {...this.state}></List>
要想把数据都给state,使用:
updateAppState = (updateObj) => { this.setState(updateObj) }
- **行内样式的书写:**style={{color:‘red’}}
- **连续解构赋值:**const { keyWordElement: { value } } = this
- 引入样式:一般在public文件中的index.html中引入。
<link rel="stylesheet" href="./css/bootstrap.css">
- react中路由的匹配都是从最外层开始匹配
- 路由组件可以在不传递数据的情况下在组件中获取this.props,而一般组件则不可以。因此在一般组件中不能使用this.porps.history
关于多个括号的问题:aaa()()
var i = 1; function f() { i++; console.log("------------- : " + i) return F; function A() { return "I love you!" } function F() { console.log('3333333') return B; function B() { return "I love you!" } } } f(); console.log("111111111111") console.log('aa', f()()()) alert(f()()())
react的@装饰模式
https://cloud.tencent.com/developer/article/1760332
装饰器相当于可以简化书写。
**正常情况下:**首先写了一个高阶组件A,在A里面把传递过来的参数组件渲染出来。然后在B组件中export default A(componentB);,这样就把B组件当成参数传递给A组件了。
如下是
componentA.js
一个高阶组件:import React, { Component } from 'react'; function A(WrappedComponent) { // 函数接收一个组件为参数,并返回一个类组件,继承自Component return class componentA extends Component { render() { return ( <div> <WrappedComponent /> </div> ); } }; } export default A;
如下
componentB.js
一个组件:import React, { Component } from 'react'; import A from './componentA'; // 引入高阶组件 class componentB extends Component { render() { return <div>我是组件B</div>; } } export default A(componentB); // 直接调用A,将组件componentB作为参数传入
使用装饰器:要引入的上面直接使用@+函数名就可以,导出的时候只写要导出的组件就行,默认就会把导出的组件当做参数传进给@+函数。
在
componentB.js
组件中import React, { Component } from 'react'; import A from './componentA'; // 引入高阶组件 @A // 直接@+函数名就可以了的 class componentB extends Component { render() { return <div>我是组件B</div>; } } export default componentB; // 这里直接返回componentB组件
关于项目中封装的类似于React.Component的使用
在项目制作过程中,我们需要对一些经常用到的公共的方法进行添加操作,这样比较麻烦,一种简单的方法就是使用一个封装的组件来代替Component导入。
例如:在Def2中添加一些方法,然后可以在Dfef1中直接使用。
注意:在Def1中使用功能的时候要引入Def2,然后就能直接使用里面的方法了。
import React, { Component } from 'react' export default class Def2 extends Component { getLog=() => { console.log('这是Def2的方法') } }
import React, { Component } from 'react' import Def2 from './Def2' export default class Def1 extends Def2 { componentDidMount(){ this.getLog() } render() { return ( <div>Def1</div> ) } }
疑问
在组件中什么时候使用箭头函数
{ item.auditState === 1 && <Button danger onClick={handleRervert(item)}>撤销</Button> } { item.auditState === 1 && <Button danger onClick={()=>handleRervert(item)}>撤销</Button> }
上面代码的不同点是一个使用的箭头函数,一个是直接写,区别是直接写的会在一开始就执行,然后后面就不再执行,箭头函数是点击的时候才会执行。
四种不同的调用方式
是否使用箭头函数以及调用的时候是否加()都是不一样的。
调用的时候:
普通函数是否加()的区别:
- 写成普通函数的形式{this.handle},不加括号则一开始不会调用,只有点击的时候才会调用。
- 普通函数加()则一开始进去就会调用,后面不再执行。
箭头函数是否加{ }的区别:
- 箭头函数加上 { } 的时候默认没有return,如果函数有返回值的话需要在{ return }才能接收返回的数据。
- 箭头函数不加 { } 则默认有return,当函数如果没有返回值,则加不加 { } 都一样。
- 箭头函数加上花括号需要写return,不加花括号不需要写,默认就有return。