JSX的语法规则
1.定义虚拟DOM时,不要写引号;
2.标签中混入JS表达式时要用{};
3.样式的类名指定不要用class,要使用className;
4.内联样式,要用style={{key:value}}的形式去写;
5.只有一个跟标签;
6.标签必须闭合;
7.标签首字母
(1).若小写字母开头,则将标签转为html中同名元素,若html中没有对应的同名元素,则报错;
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义则报错;
类
1.类中的构造器(constructor)不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写;
2.如果A类继承了B类且A类中写了构造器,那么A类构造器中super()必须要调用;
3.类中定义的方法,都是放在了类的原型对象上供实例去使用;
组件
函数式组件:
function MyComponent(){
return <h1>我是简单组件</h1>
}
ReactDOM.render(<MyComponent/>,document.getElementById('text'))
类式组件
class MyComponent extends React.Component{
render(){
return{
<h1>我是复杂组件</h1>
}
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('text'))
组件实例的三大核心属性(state)
1.state 是组件的重要属性,值为对象(可以包含多个 key-value的组合)
2.组件被称为'状态机',通过更新组件的State来更新对应的页面显示(更新渲染组件)
注意:
组件中的render方法中的this为组件实例对象;
组件自定义的方法中this为undefined,如何解决
(1).强制指定this,通过函数对象的bind() this.weater=this.weater.bind(this);
(2).通过赋值语句+箭头函数实现; weater=()=>{};
状态数据,不能直接修改或更新;需要修改时通过:this.setState({key:value})
组件实例的三大核心属性(props)
1.每个组件对象都有props属性;
2.组件标签的所有属性都保存在props中
3.通过标签属性从组件外部向组件内部传递变化的数据;
4.注意:
组件内部不要修改props数据
操作:
(1).读取某个值:this.props.name;
(2).对props中的参数进行限制
引入prop-type.js组件库
参数限制:
Person.propTypes={
name:PropTypes.string.isRequired,
age:PropTypes.number,
speak:PropTypes.func,
}
设置默认值:
Person.defaultProps={
name:'常鑫',
age:18,
}
简写(组件内部):
限制(设置默认同理)
static propTypes={
name:PropTypes.string.isRequired,
age:PropTypes.number,
speak:PropTypes.func,
}
组件实例的三大核心属性(refs)
1.字符串ref
(1).影响性能
Tips:<input type="text" ref="input1">
used: const { input1 } = this.refs;
2.回调ref
(1).内联回调ref 更新DOM时会执行两次,并没有太大影响;
Tips:<input type="text" ref={cur => this.input1 = curr }>
used:const { input1 } = this;
(2).类的函数绑定ref 首次DOM渲染,执行一次
Tips:
<input type="text" ref={this.saveInput}>
saveInput = (curr) => {
this.input1 = curr;
}
used:const { input1 } = this;
3.createRef
(1).存放ref节点,专人专用
Tips: myRef=React.createRef()
used: this.myRef.current;
React中的事件绑定
1.通过onXXX属性指定事件处理函数,注意大小写
a.React使用自定义(合成事件),而不是使用原生DOM事件 -----为了更好的兼容性
b.React中的事件绑定通过事件委托方式处理的(委托给组件最外层的元素) -----为了高效
2.通过event.target得到发生事件的DOM元素对象; -----不要过度的使用ref
React中收集表单数据
1.非受控组件 现用现取(触发事件时,才去获取数据)
2.受控组件 随着输入将输入的内容维护到状态中 (vue的双向绑定) 减少ref的使用;
高阶函数&&函数的柯里化
高阶函数:如果一个函数符合下面2个规则中的任何一个,那么该函数为高阶函数.
1.若A函数,接受的参数是一个函数,那么A就可以称之为高阶函数;
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数;
常见的高阶函数:Promise setTimeout arr.map()等等;
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接受函数最后统一处理的函数的编码形式.
tips:
<input type="text" onChange={this.saveFormDate('username')} name="username"/>
saveFormDate=(dataType)=>{
return (event)=>{
this.setState({
[dataType]:event.target.value
})
}
}
tips:
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
sum(1)(2)(3)
React的生命周期(旧)
生命周期钩子函数:
1.初始化阶段 ReactDOM.render(<Component/>,document.getElementById('text'))触发 初次渲染
(1).组件将要挂载:
componentWillMount(){}
(2).初始化渲染,状态更新之后(重复调用):
render(){}
(3).组件挂载完毕: *** 类似于Vue的created 小程序的onload 一般用于:开启定时器,发送网络请求,订阅消息
componentDidMount(){}
2.更新阶段 由this.setState()或者父组件render()触发
(4).控制组件是否更新 return true||false; tips:setState()触发
shouldComponentUpdate(){}
(5).组件将要更新; tips:forceUpdate()触发
componentWillUpdate(){}
(2).初始化渲染,状态更新之后(重复调用): *** 渲染
render(){}
(6).组件更新完毕
componentDidUpdate(){}
(7).组件将要接受props; tips:首次传递不触发
componentWillReceiveProps(props){}
3.卸载 ReactDom.unmountComponentAtNode(document.getElementById('text'))触发
(8).组件将要卸载: *** 一般做一些收尾的事:关闭定时器,取消订阅消息
componentWillUnmount(){}
生命周期钩子函数执行流程
1.加载:(1),(2),(3),
2.this.setState():(4),(5),(2),(6)
3.父组件render():(7),(4),(5),(2),(6)
React的生命周期(新)
废弃或者即将废弃
componentWillMount(){} => UNSAFE_componentWillMount(){},
componentWillUpdate(){} => UNSAFE_componentWillUpdate(){},
componentWillReceiveProps(){} => UNSAFE_componentWillReceiveProps(){},
新增:
getDerivedStateFormProps(){}
getSnapshotBeforeUpdate(){}
1.初始化阶段:由ReactDOM.render()触发
(1).constructor()
(2).getDerivedStateFormProps
(3).render()
(4).componentDidMount()
2.更新阶段:由组件内部的this.setState()或父组件更新render触发
(1).getDerivedStateFromProps
(2).shouldComponentUpdate()
(3).render()
(4).getSnapshotBeforeUpdate
(5).componentDidMount()
3.卸载组件:由ReactDOM.unmountConponentAtNode()触发
componentWillUnmount();
vue或者React中的key有什么作用
1.简单来说就是虚拟DOM的标识,
让数据状态发生改变时,React会根据[新的数据]生成[新的虚拟DOM],随后React进行[新虚拟DOM]与[旧的虚拟DOM]进行diff比较
规则如下:
a.旧的虚拟DOM中找到了新的虚拟DOM相同的key
(1).若虚拟DOM内容没有变化,直接使用之前的真实DOM
(2).若虚拟DOM中内容发生了变化,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b.旧的虚拟DOM中未找到与新的虚拟DOM相同的Key
根据数据创建新的真实DOM,随后渲染到页面上
用index作为key可能引发的问题:
1.对数据进行:逆序添加,逆序删除等破坏顺序操作:会产生没有必要的真实DOM渲染==>界面效果没问题,但效率低
2.如果结构中还包含输入类的DOM:会产生错误的DOM更新 ==>界面渲染错误
3.注意:如果不存在对数据的逆序添加,逆序删除等操作,仅用渲染列表用于展示,使用index作为key是没有问题的
开发中如何选择key?
1.最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等
2.如果确定只是简单的展示数据,用index也是可以的
React类式组件模板
class Person extends React.Component{
1.限制props中的数据类型
static propTypes={
name1:PropTypes.string.isRequired,
age1:PropTypes.number,
sex1:PropTypes.string,
}
2.设置props的数据默认值
static defaultProps={
name1:'常鑫',
age1:18,
sex1:'男'
}
3.初始化数据
state={
name:'常鑫',
age:18,
}
4.渲染
render(){
const { name, age }=this.state;
const { name1, age1, sex1 } =this.props;
return (
<ul className="Demo" onClick={this.speak} style={{fontSize:'22px',color:'red'}}>
<li>{name}</li>
<li>{age}</li>
<li>-----</li>
<li>{name1}</li>
<li>{age1}</li>
<li>{sex1}</li>
</ul>
)
}
5.事件
speak=()=>{
const { name,age }=this.state;
this.setState({
age:age+1-
})
}
}
渲染组件const p={name1:'jack',age1:20,}
ReactDOM.render(<Person {...p}/>, document.getElementById('text'))
React脚手架安装
1.全局安装: npm install -g create-react-app
2.创建项目: create-react-app 项目名称
3.切换项目目录:cd 项目名称
4.启动项目:npm start
React脚手架
public---静态资源文件夹
favicon.icon ----- 网站页签图标
index.html ----- 主页面
logo.192.png ----- logo图
logo.512.png ----- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt ----- 爬虫协议文件
src---源码文件夹
App.css ----- App组件样式
App.js ----- App组件
app.test.js ----- 用于给App测试
index.css ----- 公共样式
index.js ----- 入口文件
logo.svg ----- logo图
reportWebVitals.js ----- 页面性能分析文件(需要web-vitals库的支持)
setupTest.js ----- 组件单元测试的文件(需要jest-dom库的支持)
React脚手架初始化配置
目录结构:
-public
-index.html 主页面
-favicon.icon
-src
-App.js App外壳组件
-index.js 入口文件
-Component 组件文件夹
-各类组件文件夹
--index.jsx
--index.css
-pages 路由组件
-各类路由组件文件夹
--index.jsx
--index.css
样式的模块化
xxx.module.css
引入: import xxx form '模块换样式路径'
used: <div className={xxx.样式名}></div>
React常用开发快捷键
rcc =>快速构建类式组件模板
rfc =>快速构建函数式组件模板
cdm => componentDidMount
cwun =>componentWillUnmount
脚手架配置代理
方法一:
在package.json中追加如下配置 "proxy":"http://localhost:5000"
tips:
(1). 优点:配置简单,前端请求资源时可以不加任何前缀。
(2). 缺点:不能配置多个代理。
(3). 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二:
1.第一步:创建代理配置文件 在src下创建配置文件:src/setupProxy.js
2.编写setupProxy.js配置具体代理规则:
const 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': ''}
})
)
}
说明:
1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
2. 缺点:配置繁琐,前端请求资源时必须加前缀。
消息订阅发布
借助工具库:PubSubjs
下载:npm install pubsub-js --save
used:
import PubSub from 'pubsub-js'
PubSub.subscribe('delete',function(data){}) //订阅消息
PubSub.publish('delete',data) //发布消息
Fetch的使用
search= async ()=>{
// 发送网络请求Fetch(未优化)
//#region
// fetch(`/search/users?q=${data}`).then(
// res=>{
// // 将连接成功之后的数据通过 原型链上的json() 传递出去; 为promise()类型;
// return res.json()
// },
// err=>{
// console.log(err)
// // 中断promise链
// return new Promise(()=>{})
// }
// ).then(
// res=>{
// console.log(res)
// },
// err=>{
// console.log(err)
// }
// )
//#endregion
// 发送网络请求Fetch(优化后)
try {
const res = await fetch(`/search/users?q=${data}`)
const reault = await res.json()
PubSub.publish('updata', { users: reault.items, isLoading: false })
} catch (err) {
PubSub.publish('updata', { err: '请求超时,请重试!!!', isLoading: false })
}
}
tips:
1.Fetch原生函数,不借助xhr发送ajax请求
2.老版浏览器可能不支持使用Fetch
路由的基本使用
npm i react-router-dom 安装 react-router-dom
1.明确好界面的导航区、内容区
2.导航区的a标签改为Link标签 to='地址栏中展示的路径'
<Link to="/xxxxx">Demo</Link> 类似于vue中的<router-link to="/xxx"></router-link> 不含选中样式
3.展示区写Route标签进行路径匹配 path='地址栏中注册路径' component={路由组件}
<Route path="/XXX" component={Demo}> 类似于vue中的<router-view></router-view>
4.<App>的最外侧包裹一个 <BrowserRouter> 或者 <HashRouter>
一般组件与路由组件的区别
1.写法不同
一般组件:<Demo>
路由组件:<Route path="/demo" component={Demo}>
2.存放位置不同
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时,传递了什么就能收到什么
路由组件:接收到三个固定的属性
(1).history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
(2).location:
pathname: "/about"
search: ""
state: undefined
(3).match:
params: {}
path: "/about"
url: "/about"
NavLink与navLink 封装
<NavLink activeClassName='active' to="/xxxxx">Demo</NavLink> 含选中样式
NavLink的封装:将NavLink组件进行二次封装,减少代码量;
封装:<NavLink activeClassName="actives" className="list-group-item" {...this.props} />
使用:<MyNavLink to="/about">About</MyNavLink>
switch的使用
path和component是一一对应的;
switch可以提高路由匹配效率(单一匹配)
路由样式丢失
当路由为多级路由时;路由组件加载完毕再次刷新时会出现样式错乱
解决方法:
(1).public/index中引入样式文件时不写 ./ 写 /
(2).public/index中引入样式文件时不写 ./ 写 %PUBLIC_URL%
(3).修改路由模式:将index.js中的BrowserRouter改为HashRouter
路由的模糊匹配与精准匹配
<Router exact={true} path="/about" component="about"> exact={true}开启精准匹配 默认为模糊匹配
模糊匹配规则:"输入路径"的路径必须包含"匹配的路径"只多不少,且顺序一致
<NavLink to="/about/a/c"> <Route path="/about" component={About}> ==>成功匹配
<NavLink to="a/about"> <Route path="/about" component={About}> ==>失败匹配
严格匹配:
<NavLink>中的to=""的路径与<Router>中的path=""完全一致才匹配
tips:严格匹配不能随便开启,需要开启时在开启,有的时候开启会导致无法匹配到二级路由
redireact的使用
一般写在路由注册的最下方,当所有路由都没有匹配时,跳转到redireacr指定的路由
<Switch>
<Route path="/about" component={About}>About</Route>
<Route path="/home" component={Home}>Home</Route>
<Redireact to="/about">
</Switch>
嵌套路由的使用
1.注册的子路由要写上父路由的path值
路由链接:<NavLink to="home/news">
路由注册:<Route path="home/news" component={News}>
2.路由的匹配按照注册路由的顺序进行
路由传参
1.params参数:需要在路由注册时提前声明接受
注册路由时:<Route path="/home/message/messageinfo/:id/:title" component={MessageInfo}/>
跳转链接时:<Link to={`/home/message/messageinfo/${item.id}/${item.title}`}>{item.title}/</Link>
参数接受时: const { id, title } = this.props.match.params;
2.search参数:无需提前声明接受
注册路由时:<Route path="/home/message/messageinfo" component={MessageInfo}/>
路由链接时:<Link to={`/home/message/messageinfo/?id=${item.id}&title=${item.title}`}>{item.title}</Link>
参数接受时:const search = this.props.location.search;
const { id, title } = qs.parse(search.slice(1)) //获取到的search时urlencoded编码,需要qs转换
3.state参数:无需提前声明接受,不会再地址栏中显示参数
注册路由时:<Route path="/home/message/messageinfo" component={MessageInfo}/>
路由链接时:<Link to={{pathname:'/home/message/messageinfo',state:{id:item.id,title:item.title}}}>{item.title}</Link>
接受参数时:const { id, title } = this.props.location.state;
qs常用api
stringify:
let obj={name:'常鑫',age='12'}
console.log(qs.stringIfy(obj)) // name=常鑫&&age=12
parse:
let str='name=常鑫&&age=12'
console.log(qs.parse(str)) //{name:'常鑫',age='12'}
路由跳转的两种模式
push(默认): 压栈处理,会保留浏览记录
replace: 替换处理,会替换当前浏览记录
<Link replace to={{pathname:'/home/message/messageinfo',state:{id:item.id,title:item.title}}}> //replace 开启replace模式
导航式路由跳转和编程时路由跳转
导航式路由跳转:需要借助<Link>,<NavLink>等组件进行路由组件切换 ,类似于<a>标签;
编程时路由跳转:
路由组件api:
this.props.history.push('path',state) //push跳转
this.props.history.replace('path',state) //replace跳转
this.props.history.goBack() //路由回退
this.props.history.goForward() //路由前进
this.props.history.go(num) //路由前进 1前进1步 -1后退一步
withRouter(components) //接受一个组件,给一般组件添加路由组件特有的三个属性; hitory ,loaction ,match 返回值是一个新组件
BrowserRouter与HashRouter的区别
1.底层原理不同:
BrowserRouter使用的式H5的history API 不兼容ie9及一下把呢吧
HashRouter 使用的是URL的哈希值
2.url的表现形式不一样
BrowserRouter的路径中没有#
HashRouter的路径中含有#
3.刷新后对路由state参数的影响
BrowserRouter没有任何影响
HashRouter刷新后会导致路由的state参数丢失
4.备注:HashRouter可以用于解决一些路径错误相关的问题
antdUI库的使用 3.0x
//安装ant-design UI库
npm i antd
//按需引入所需要的antd组件
import { Button } from 'antd'
1.安装antd组件“按需引入”插件
npm i react-app-rewired customize-cra
2.修改package.json
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
3.安装babel-plugin-import 插件
npm i babel-plugin-import
4.在根目录下创建config-overrides.js
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
antd自定义主题 3.0x
1.安装less-loade
npm i less less-loader
2.修改config-overrides.js
- const { override, fixBabelImports } = require('customize-cra');
+ const { override, fixBabelImports, addLessLoader } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
- style: 'css',
+ style: true,
}),
+ addLessLoader({
+ javascriptEnabled: true,
+ modifyVars: { '@primary-color': '#1DA57A' },
+ }),
);
ant的使用及自定义主题4.0
1.安装ant-design UI库
npm i antd
2.修改 src/App.js引入组件,引入样式
import { Button } from 'antd'
import './App.less';
3.新建App.less于app.jsx同级,并引入antd.less
/* App.less */
@import '~antd/dist/antd.less';
4.安装@craco/craco和craco-less插件
npm i @craco/craco
npm i craco-less
5.修改package.json文件
/* package.json */
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}
6.新建文件craco.config.js
/* craco.config.js */
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' }, //变量所代表的颜色
javascriptEnabled: true,
},
},
},
},
],
};
详细地址:https://ant.design/docs/react/use-with-create-react-app-cn
redux的使用
redux:用于状态管理,类似于与vuex,存放一些公共的共享数据
redux的三个核心属性:
action
动作对象
包含两个属性{type:'标识属性,字符串,唯一,必要属性',data:'数据属性,值类型任意,可选属性'}
tips:{type:'ADD_STUDENT',data:{name:'chuancey',age:18}}
reducer
用于初始化状态,加工状态
加工时,根据旧的state和action,产生新的state的纯函数
store
将state,action,reducer联系在一起的对象
如何得到此对象?
1).import {createStore} from 'redux'
2).import reducer from './reducers'
3).const store =createStore
此对象的功能?
1).getState();得到state,
2).dispatch(action):分发action,触发reducer调用,产生新的state
3).subscrible(listener):注册监听,产生新的state时,自动调用
react-redux的使用
所有UI组件都应该包裹一个容器组件,他们是父子关系
容器组件是真正与redux打交道的,里边可以随便使用redux的api store.dispath(action),store.getState();
UI组件不能使用任何redux的api
容器组件会传给UI组件: (1).redux中保存的所有状态 (2).用于操作的方法
容器组件给UI组件传递:状态,操作的方法,均通过Props传递 容器组件版存放在src目录下的container文件夹中
纯函数和高阶函数
是一类特殊的纯函数:只要是同样的输入(实参),必定得到同样的输出(返回)
必须遵守一下的约束
不得改写参数数据
不会产生副作用,例如网络请求,输入和输出设备
不能调用Date.now()或者Math.random()等不纯的方法
redux的reducer函数必须是一个纯函数
redux开发者工具的使用
npm i redux-devtools-extension
在store中引入 { composeWithDevTools } from 'redux-devtools-extension'
createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
使用umi 创建react项目
1.安装tyarn
$ npm i yarn tyarn -g
2.使用tyarn创建 umi
tyarn cerate umi 文件项目
案例总结
todoList案例总结
1.拆分组件,实现静态组件,注意className/style写法
2.动态初始化列表,如何确定将数据放在那个组件中的state中?
(1).某个组件使用,放在自身的state中
(2).某些组件使用,放在他们公用的父组件的state中
3.父子组件通信
父与子传递参数:使用props传递
子与父传递参数:通过props传递,要求父提前传递给子组件一个函数
4.注意defaultChecked 和check的区别 defaultChecked第一次渲染 checked渲染完之后不能修改需要绑定onChange
5.状态在哪里,操作状态的方法放哪里
github搜索案例总结
1.设计状态时要考虑全面 例如带有网络请求的组件 要考虑请求失败怎么办
2.es6小知识点:结构+重命名
let obj = {a:{b:1}}
const {a} = obj //传统解构赋值
const {a:{b}} = obj //联系解构赋值
const {a:{b:c}} = obj //二次解构赋值并改名
3.消息订阅与发布消息
先订阅,在发布
适用于任意组件间通信
要在组件的componentWillUnMount中取消订阅
4.Fetch发送请求(关注分离思想)
try {
const res = await fetch(`/search/users?q=${data}`)
const reault = await res.json()
PubSub.publish('updata', { users: reault.items, isLoading: false })
} catch (err) {
//错误统一处理
PubSub.publish('updata', { err: '请求超时,请重试!!!', isLoading: false })
}
redux
(1).去除Count自身的状态
(2).src下建立redux
-redux
-store.js //创建store,
-count_reducer.js //创建reducer, 定义为组件服务的reducer
/*完整版新增*/
-count_acton.js //创建action, 定义Action对象
-constant.js //便于管理的同时,防止单词写错
(3).store.js:
1).引入reduce中的createStore函数,创建一个store,
2).createState调用时传入一个为其服务的reducer
3).暴露store对象
(4).count_reducer.js
1).reducer的本质上是一个函数,接受preState,action,返回加工后的状态
2).reducer有两种作用:初始化状态,加工状态
3).reducer第一次调用时,是store自动触发的,传递的是:
preState:undefined
action:{type:'@@INIT@@',data:''}
(5).在index.js中检测store中状态的改变,一但发生改变就重新渲染<App/>
tips:redux只负责管理状态,至于状态的改变驱动页面的展示,要靠自己
redux(异步action)
明确:延迟的动作不想交给组件,想交给action
何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回.
具体编码:
npm i redux-thunk,并配置到store.js中
import thunk from "redux-thunk";
import { createStore,applyMiddleware } from "redux";
export default createStore(CountReducer,applyMiddleware(thunk))
创建action的函数不在返回一个对象而是一个函数,该函数中写异步任务
异步任务完成之后,分发一个同步的action去真正操作数据
tips:异步action中不是必须要写的,完全可以自己等待异步任务的结果,再去分发同步action
react-redux
明确两个概念:
UI组件:不能使用redux的任何Api ,只负责页面的展示,交互等
容器组件:负责与Redux进行通信,将结果交给UI组件
如何创建容器组件 ---靠react-redux的connect函数
connect(mapStateToProps,mapDispatchToProps)(UI组件)
mapStateToProps:映射状态,返回一个对象
mapDispatchToProps:映射操作状态的方法,返回一个对象
tips1:容器组件中的store是靠在组件中用props传进去的,而不是在容器组件中直接引入的
tips2:mapDispatchToProps 传递两种参数 1.function 2.对象 传递action对象,react-redux会自动调用dispatch 来执行此action
react-redux(优化)
(1).容器组件和UI组件整合为一个组件
(2).无需自己给容器组件添加state,给<App/> 包裹一个<Provider store={store}>即可.
(3).使用了react_redux后不需要自己在检测redux中状态的改变,容器组件可以自动完成
(4).mapStateToProps可以简单的写出一个对象啊
(5).一个组件和redux"打交道"要经过的几个步骤
1).定义好UI组件 --不暴露
2).引入connect生成一个容器组件,并暴露,写法如下:
connect(
state=>{},
{key:value(action)}
)(UI组件)
3).在UI组件中通过this.props.xxx读取或操作状态
react-redux(数据共享)
(1).定义一个Person类,和Count组件通过redux共享数据
(2).为Person组件编写:reducer,action,配置constant常量
(3).Person的reducer和Count的Reducer要使用combineReducers进行合并,合并之后为一个对象
(4).交给store的是总的Reducer,最后注意在组件中取出状态的时候,记得"取到位"
react-redux(最终版)
变量命名时,一定要尽量避免简写形式
在reducer中创建index.js用于汇总所有的reducer,并暴露出去
react扩展
1. setState
setState更新状态的2种写法
(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取
2. lazyLoad
路由组件的lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
3. Hooks
1). React Hook/Hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
2). 三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
3). State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4). Effect Hook
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
5). Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
4. Fragment
使用
<Fragment><Fragment>
<></>
作用
可以不用必须有一个真实的DOM根标签了
5. Context
理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用
1) 创建Context容器对象: const XxxContext = React.createContext() 2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据: <xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider> 3) 后代组件读取数据: //第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context // 读取context中的value数据 //第二种方式: 函数组件与类组件都可以 <xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
注意
在应用开发中一般不用context, 一般都用它的封装react插件
6. 组件优化
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来优化
7. 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}
8. 错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发static getDerivedStateFromError(error) { console.log(error); // 在render之前触发 // 返回新的state return { hasError: true, };}componentDidCatch(error, info) { // 统计页面的错误。发送请求发送到后台去 console.log(error, info);}
9. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props: (1).children props (2).render props 2.消息订阅-发布: pubs-sub、event等等 3.集中式管理: redux、dva等等 4.conText: 生产者-消费者模式
比较好的搭配方式:
父子组件:props 兄弟组件:消息订阅-发布、集中式管理 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
签体传入结构 使用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}
8. 错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发static getDerivedStateFromError(error) { console.log(error); // 在render之前触发 // 返回新的state return { hasError: true, };}componentDidCatch(error, info) { // 统计页面的错误。发送请求发送到后台去 console.log(error, info);}
9. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props: (1).children props (2).render props 2.消息订阅-发布: pubs-sub、event等等 3.集中式管理: redux、dva等等 4.conText: 生产者-消费者模式
比较好的搭配方式:
父子组件:props 兄弟组件:消息订阅-发布、集中式管理 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)