简介
英文官网: https://reactjs.org/
中文官网: https://react.docschina.org/
介绍描述
用于动态构建用户界面的 JavaScript 库(只关注于视图)
由Facebook开源
React的特点
声明式编码
组件化编码
React Native 编写原生应用
高效(优秀的Diffing算法)
React高效的原因
使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
DOM Diffing算法, 最小化页面重绘。
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入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>
<script type="text/babel"> /* 此处一定要写babel */
//1.创建虚拟DOM
//使用JS创建
// const VDOM = React.createElement('h1', {id:'qgg'},React.createElement('span', {},'Hello World'))
//使用JSX创建
const VDOM = (/* 此处一定不要写引号,因为不是字符串 */
<h1 id='qgg'>
<span>Hello World</span>
</h1>)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
- 本质是Object类型的对象(一般对象)
- 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
- 虚拟DOM最终会被React转化为真实DOM,呈现在页面上
组件名必须首字母大写
虚拟DOM元素只能有一个根元素
虚拟DOM元素必须有结束标签
JSX
1.定义虚拟DOM时,不要写引号。
2.标签中混入JS表达式时要用{}。
3.样式的类名指定不要用class,要用className。
4.内联样式,要用style={{key:value}}的形式去写。
5.只有一个根标签
6.标签必须闭合
7.标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
定义组件
函数式组件
//1.创建函数式组件
function MyComponent(){
console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
执行了ReactDOM.render(…之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
类式组件
//1.创建类式组件
class MyComponent extends React.Component {
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
执行了ReactDOM.render(…之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
组件实例三大属性
state
原始
//1.创建组件
class Weather extends React.Component {
//构造器调用几次? ———— 1次
constructor(props) {
console.log('constructor');
super(props)
//初始化状态
this.state = { isHot: false, wind: '微风' }
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this) //将绑定在实例原型对象上的方法绑定在实例上
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render() {
console.log('render');
//读取状态
const { isHot, wind } = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather() {
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({ isHot: !isHot })
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
状态必须通过setState进行更新,且更新是一种合并,不是替换。
简写
//1.创建组件
class Weather extends React.Component {
//初始化状态
state = { isHot: false, wind: '微风' }
render() {
const { isHot, wind } = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//自定义方法————要用赋值语句的形式 + 箭头函数
changeWeather = () => {
const isHot = this.state.isHot
this.setState({ isHot: !isHot })
}
}
- 箭头函数的this指向定义时所在的外层第一个普通函数,跟使用位置没有关系。
- 被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变
props
类
ReactDOM.render(<Person name="jerry" age={19} sex="男" />, document.getElementById('test1'))
const p = { name: '老刘', age: 18, sex: '女' }
ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
对象的解构只有在这里才能用,此处的{}不是指对象的浅拷贝
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
必须先指定接收的属性类型再指定是否必须
React v15.5 开始已弃用在React上携带propTypes这样React太重了
现在需引入prop-types,用于对组件标签属性进行限制
constructor(props){
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super(props)
console.log('constructor',this.props);
}
简写
static propTypes = {...}
static defaultProps = {...}
函数
//创建组件
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
refs
字符串
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
回调函数
<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>
更新时会传入两次,第一次传入null
createRef
myRef = React.createRef()
...
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
...
this.myRef.current.value
事件处理
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
收集表单数据
非受控组件
现用现取
受控组件
相当于vue中的双向数据绑定
//初始化状态
state = {
username:'', //用户名
password:'' //密码
}
//保存用户名到状态中
saveUsername = (event)=>{
this.setState({username:event.target.value})
}
高阶函数
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
函数柯里化
//通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
生命周期函数
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
//强制更新
this.forceUpdate()
OLD
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaxV5j0b-1654776804145)(D:\黑马程序员$React\react全家桶资料\02_原理图\react生命周期(旧)].png)
初始化阶段: 由ReactDOM.render()触发 初次渲染
constructor()
componentWillMount()
render()
componentDidMount()
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
更新阶段: 由组件内部this.setSate()或父组件render触发
shouldComponentUpdate()
componentWillUpdate()
render() =====> 必须使用的一个
componentDidUpdate()
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
挂载时:先执行构造器(constructor)=》组件将要挂载(componentWillMount)=》组件挂载渲染(render)=》组件挂载完成(componentDidMount)=》组件销毁(componentWillUnmount)
组件内部状态更新:组件是否应该更新(shouldComponentUpdate)=》组件将要更新(componentWillUpdate)=》组件更新渲染(render)=》组件更新完成(componentDidUpdate)
强制更新:调用this.forceUpdate(),这个api和setState一样都是react自带的,一般这个强制更新很少用,它的执行流程就是比上述的正常更新流程少一步询问是否更新(shouldComponentUpdate)
父组件重新render:调用组件将要接收新props(componentWillReceiveProps)=》组件是否应该更新(shouldComponentUpdate)=》组件将要更新(componentWillUpdate)=》组件更新渲染(render)=》组件更新完成(componentDidUpdate)
注意:上述加粗的函数,只有在父组件状态发生改变了,重新调用render时才会调用子组件的componentWillReceiveProps函数,父组件第一次引用子组件的时时不会调用的
NEW
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6e4vKIf-1654776804150)(D:\黑马程序员$React\react全家桶资料\02_原理图\react生命周期(新)].png)
新版生命周期函数和旧版的差别:
新版即将废弃老的3个钩子(componentWillMount、componentWillReceiveProps、componentWillUpdate)
新增了2个钩子(getDerivedStateFromProps、getSnapshotBeforeUpdate)
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
DOM的Diff算法
虚拟DOM中的key的作用:
当状态中的数据发生改变时,react会根据【新数据】生成【新虚拟DOM】,随后react会进行【新虚拟DOM】和【旧虚拟DOM】的diff算法比较,具体的比较规则如下:
若【旧DOM】中找到了与【新DOM】相同的key,则会进一步判断两者的内容是否相同,如果也一样,则直接使用之前的真实DOM,如果内容不一样,则会生成新的真实DOM,替换掉原先的真实DOM
若【旧DOM】中没找到与【新DOM】相同的key,则直接生成新的真实DOM,然后渲染到页面
用index作为key可能引发的问题
若对数据进行:逆序添加、逆序删除等破坏顺序的操作时会产生不必要的真实DOM更新,造成效率低下
如果结构中还包含输入类的dom,会产生错误dom更新,出现界面异常
开发中如何选择key
最好选中标签的唯一标识id、手机号等
如果只是简单的展示数据,用index也是可以的
React脚手架
使用create-react-app(脚手架工具)创建一个初始化项目
1、下载脚手架工具:npm i -g create-react-app
2、创建引用:create-react-app my-app
3、运行应用:cd my-app(进入应用文件夹),npm start(启动应用)
public ---- 静态资源文件夹
favicon.icon ------ 网站页签图标
index.html -------- 主页面
logo192.png ------- logo图
logo512.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库的支持)
setupTests.js
---- 组件单元测试的文件(需要jest-dom库的支持)
React脚手架配置代理
简单
在package.json中追加如下配置
“proxy”:“http://localhost:5000”
1、优点:配置简单,前端请求资源可以不加任何前缀
2、缺点:不能配置多个代理(如果请求的不同服务器就不行)
3、工作方式:当请求了自身3000端口不存在的资源时,那么会转发给5000端口(优先会匹配自身的资源,如果自己有就不会请求5000端口了)
麻烦
创建代理配置文件
在src下创建配置文件:src/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': ''}
})
)
}
1、优点:可以配置多个代理,可以灵活控制请求是否走代理
2、缺点:配置繁琐,前端请求资源时必须加前缀
消息订阅-发布机制
工具库:PubSubJs
下载:npm install pubsub-js --save
先引入:import PubSub from “pubsub-js”
要接收数据方订阅 | PubSub.subscribe(‘消息名’,(data)=>{ console.log(data) }) |
---|---|
传递数据方发布 | PubSub.publish(‘消息名’,data) |
路由
一个路由就是一个映射关系
key永远为路径,value可能是function或者component
路由分类
后端路由
后端路由的key还是路径,只不过value是上述说的function
注册路由:router.get(path, function(req,res){…})
工作过程:当node接收到一个请求时,会根据请求路径去匹配对应的路由,然后调用对应路由中的函数来处理请求,返回响应数据
前端路由
浏览器端路由,value是对应组件(component),用于展示页面内容
注册路由:
工作过程:当浏览器path变为/test时,当前路由组件就会变成Test组件
react-router-dom
内置组件
路由的基本使用
//导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link>
//展示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
//<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
路由组件与一般组件
写法不同
一般组件:
路由组件:
存放位置不同:
一般组件:components
路由组件: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
NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
Switch的使用
- 通常情况下,path和component是一一对应的关系。
- Switch可以提高路由匹配效率(单一匹配)。
- 匹配一个成功后,后续相同路径组件也不会生效了
解决多级路径刷新页面样式丢失的问题
- public/index.html 中 引入样式时不写 ./ 写 / (常用)
- public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
- 使用HashRouter
路由的严格匹配与模糊匹配
-
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
-
开启严格匹配:
-
严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Redirect的使用
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
嵌套路由
- 注册子路由时要写上父路由的 path 值
- 路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数
params参数
路由链接(携带参数) | <Link to=‘/demo/test/tom/18’}>详情</Link> |
---|---|
注册路由(声明接收) | <Route path=“/demo/test/:name/:age” component={Test}/> |
接收参数 | this.props.match.params |
search参数
路由链接(携带参数) | <Link to=‘/demo/test?name=tom&age=18’}>详情</Link> |
---|---|
注册路由(无需声明,正常注册即可) | <Route path=“/demo/test” component={Test}/> |
接收参数 | this.props.location.search |
备注 | 获取到的search是urlencoded编码字符串,需要借助querystring解析 |
state参数
路由链接(携带参数) | <Link to={{pathname:‘/demo/test’,state:{name:‘tom’,age:18}}}>详情</Link> |
---|---|
注册路由(无需声明,正常注册即可) | <Route path=“/demo/test” component={Test}/> |
接收参数 | this.props.location.state |
备注 | 刷新也可以保留住参数 |
编程式路由导航
借助this.props.history对象上的API对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
BrowserRouter与HashRouter的区别
底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!
push与replace
默认开启的是push模式,push模式就是说每次的点击跳转改变路径,都是往浏览器历史记录的栈中不断追加一条记录,然后你点回退按钮时,它会指向当前栈顶记录的前一条,replcae模式就是说替换掉当前的那条记录,然后你点回退的时候,就不会显示上次被替换掉的那条记录了,只会显示上上条记录,那要怎么设置为replace模式呢?直接在**<Link replace to=‘XXX’>**标签上添加一个replace属性即可
withRouter
作用:它就是专门解决在一般组件中想要使用路由组件的那几个API的这个问题的,它接收一个一般组件,然后调用后,该一般组件身上也有了路由组件的history、match等属性
import {withRouter} from 'react-router-dom'
class Header extends Component {}
export default withRouter(Header)
//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
//withRouter的返回值是一个新组件
UI组件库
material-ui(国外)
官网: http://www.material-ui.com/#/
Github: https://github.com/callemall/material-ui
ant-design(国内蚂蚁金服)
官网: https://ant.design/index-cn
Github: https://github.com/ant-design/ant-design/
redux
简介
-
它是专门做状态管理的js库,不是react插件库
-
它可以用在angular、vue、react等项目中,但与react配合用到最多
-
集中式管理react应用中多个组件共享的状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NLlNdpoR-1654776804155)(D:\黑马程序员$React\react全家桶资料\02_原理图\redux原理图.png)]
基本使用
-
去除Count组件自身的状态
-
src下建立:
redux/
store.js
count_reducer.js
-
store.js:
//引入redux中的createStore函数,创建一个store
import {createStore} from 'redux'
//createStore调用时要传入一个为其服务的reducer
import countReducer from './count_reducer'
//暴露store对象
export default createStore(countReducer)
- count_reducer.js:
/*
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数,返回加工后的状态
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
3.reducer有两个作用:初始化状态,加工状态,reducer被第一次调用时,是store自动触发的
*/
import {
INCREMENT,
DECREMENT
} from "./constant";
const initState = 0
export default function countReducer(previousState = initState, action) {
console.log(previousState, action); //undefined {type: '@@redux/INIT1.m.x.g.h.b'}
const {
type,
data
} = action
switch (type) {
case INCREMENT:
return previousState + data
case DECREMENT:
return previousState - data
default:
return previousState
}
}
- 在index.js中监测store中状态的改变,一旦发生改变重新渲染**<App/>**
store.dispatch({type:'increment',data:value*1})
ReactDOM.render( < App / > , document.getElementById('root'))
store.subscribe(() => {
ReactDOM.render( < App / > , document.getElementById('root'))
})
- 备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
action
//count_action.js 专门用于创建action对象
export const increase = data => ({type: INCREMENT, data})
异步action
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
export default createStore(countReducer, applyMiddleware(thunk))
//count_reducer.js
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
//异步任务有结果后,分发一个同步的action去真正操作数据
setTimeout(()=>{
//异步action中一般都会调用同步action
dispatch(createIncrementAction(data))
},time)
}
}
react-redux
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEB5n4W4-1654776804156)(D:\黑马程序员$React\react全家桶资料\02_原理图\react-redux模型图.png)]
react-redux基本使用
//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
/*
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:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
react-redux优化
- 容器组件和UI组件整合一个文件
- 无需自己给容器组件传递store,给<App/>包裹一个**<Provider store={store}></Provider>**即可
- 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作
- mapDispatchToProps也可以简单的写成一个对象
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
-
一个组件要和redux“打交道”要经过哪几步?
定义好UI组件—不暴露
引入connect生成一个容器组件,并暴露,写法如下:connect( state => ({key:value}), //映射状态 {key:xxxxxAction} //映射操作状态的方法 )(UI组件)
在UI组件中通过this.props.xxxxxxx读取和操作状态
react-redux数据共享版
为Person组件编写:reducer、action,配置constant常量。
重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'
//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
he:countReducer,
rens:personReducer
})
交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。
react-redux开发者工具的使用
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
react-redux最终版
- 所有变量名字要规范,尽量触发对象的简写形式。
- reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer
项目打包运行
npm run build 它会生成一个build文件夹
服务器搭建:
- 用node+express可以搭建一个简单的服务器
- 需要用到一个库serve,使用前需要先下载npm i serve -g,然后进入build文件夹中执行serve即可
拓展
setState
对象式
setState(stateChange, [callback])------对象式的setState
- stateChange为状态改变对象(该对象可以体现出状态的更改)
- callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
//异步调用
this.setState({count:count+1},()=>{
//更新后的值
console.log(this.state.count);
})
//更新前的值
console.log(this.state.count);
函数式
setState(updater, [callback])------函数式的setState
- updater为返回stateChange对象的函数。
- updater可以接收到state和props。
- callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
this.setState( state => ({count:state.count+1}))
使用原则:
- 如果新状态不依赖于原状态 ===> 使用对象方式
- 如果新状态依赖于原状态 ===> 使用函数方式
- 如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取
lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
import Loading from './Loading'
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<Loading />}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
Hooks
React Hook/Hooks是什么
- Hook是React 16.8.0版本增加的新特性/新语法
- 可以让你在函数组件中使用 state 以及其他的 React 特性
State Hook
让函数组件也可以有state状态, 并进行状态数据的读写操作
const [xxx, setXxx] = React.useState(initValue)
useState()说明 | |
---|---|
参数 | 第一次初始化指定的值在内部作缓存 |
返回值 | 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数 |
setXxx()2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
Effect Hook
用于模拟类组件中的生命周期钩子
React.useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
Ref Hook
可以在函数组件中存储/查找组件内的标签或任意其它数据
const myRef = React.useRef()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gPRbdsO3-1654776804157)(C:\Users\邱嘎噶\AppData\Roaming\Typora\typora-user-images\image-20220605171554829.png)]
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
export default function Son(props) {
// 使用refs
const qggRef = React.useRef()
const showRef = () => {
alert(qggRef.current.innerHTML)
}
// 使用state
const [qggState, setQggState] = React.useState('qggState')
const changeQggState = () => {
setQggState(value => value + 'nb')
}
const death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
// 使用Effect模仿三个生命周期钩子
React.useEffect(() => {
const timer = setInterval(
() => {
setQggState(value => value + 'nb')
}, 1000)
return () => {
clearInterval(timer)
}
}, [])
return (
<div className="son">
<span>Son</span>
<ul>
<li ref={qggRef}>state:{qggState}</li>
<li>props:{props.qggProps}</li>
<li><button onClick={showRef}>refs</button></li>
</ul>
<button onClick={changeQggState}>setQggState</button><br />
<button onClick={death}>death</button><br />
</div>
)
}
Fragment
这个标签就是用在有时页面结构层级太多,而且有些都是语法要求,实际没意义的结构层级(return()中的根节点就是这个情况),这时你就可以用Fragment标签,当然<></>在一般情况下和Fragment标签作用相同,当时有一点不一样,就是Fragment标签能接收一个key属性,而<></>什么属性都不能接收
<Fragment><Fragment>
import React, { Component, Fragment } from 'react'
<Fragment key={1}>
<input type="text"/>
<input type="text"/>
</Fragment>
<></>
Context
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
-
创建Context容器对象:
const XxxContext = React.createContext()
-
渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
-
后代组件读取数据:
//第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context // 读取context中的value数据 //第二种方式: 函数组件与类组件都可以 <xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
组件优化
Component的2个问题
- 只要执行setState(),即使不改变状态数据,组件也会重新render() ==> 效率低
- 只要当前组件重新render(),就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
原因
Component中的shouldComponentUpdate()总是返回true
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
解决
重写shouldComponentUpdate()方法
比较新旧state或props数据,如果有变化才返回true,如果没有返回false
缺点:得一个一个写
父组件
shouldComponentUpdate(nextProps,nextState){
// console.log(this.props,this.state); //目前的props和state
// console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.state.carName === nextState.carName
}
子组件
shouldComponentUpdate(nextProps,nextState){
console.log(this.props,this.state); //目前的props和state
console.log(nextProps,nextState); //接下要变化的目标props,目标state
return !this.props.carName === nextProps.carName
}
PureComponent
PureComponent重写了shouldComponentUpdate(),只是进行state和props数据的浅比较
只有state或props数据有变化才返回true,如果只是数据对象内部数据变了,返回false
render props
向组件内部动态传入带内容的结构(标签)
children props
通过组件标签体传入结构
//父组件
<A>
<B>xxxx</B>
</A>
//子组件
{this.props.children}
问题: 如果B组件需要A组件内的数据 ==> 做不到
render props
通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
//祖组件
<A render = {data => <C data={data} />}></A>
//A组件
{this.props.render(内部state数据)}
//C组件读取A组件传入的数据
{this.props.data}
错误边界
Error boundary:用来捕获后代组件错误,渲染出备用页面
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:getDerivedStateFromError配合componentDidCatch
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
hasError: '' //用于标识子组件是否产生错误
}
// 当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
// 在render之前触发
// 返回新的state
static getDerivedStateFromError(error) {
console.log(error);
return { hasError: error }
}
componentDidCatch() {
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
</div>
)
}
}
组件通信方式总结
props | children props render props |
---|---|
消息订阅-发布 | pubs-sub、event |
集中式管理 | redux、dva |
Context | 生产者-消费者模式 |
搭配方式
父子组件 | props |
---|---|
兄弟组件 | 消息订阅-发布、集中式管理 |
祖孙组件(跨级组件) | 消息订阅-发布、集中式管理、Context(开发用的少,封装插件用的多) |