介绍:
项目搭建逻辑:1)项目中index.html中渲染的内容。2)看main.js中绑定的组件渲染
React框架 由Facebock开源的一款前端框架。
工作中使用的版本:
react:16.2.0(太老了,都更新到16.14.0-18.0.2)
ant:3.x 蚂蚁研发(0.9x-1.x-2.x-3.x-4.x)
React+anted+axios+redux+mobx
推荐可以在React中使用的工具库
(1)UI组件库 https://ant.design/components/table-cn/ (最新版本4.x)
(2)状态管理工具Dvajs:是基于 redux , react-redux ,redux-saga 的状态管理工具
(3)拖拽库dnd
(4)可视化图表 echarts-for-react(基echarts 封装的图表库,能够满足基本的可视化图表需求)https://git.hust.cc/echarts-for-react/ (公司用 的是echarts )
(5)链接生成二维码:qrcode.react(import QRCode from ‘qrcode.react’)
<QRCode fgColor={'pink'} size={100} value="https://juejin.cn/user/2418581313687390" />
内容:
一、基础知识
// 支持JSX写法,将js、css、html写在一起
// 组件实例
// 1、类组件(类似es6中的类。就是构造函数的扩展)
// 特点:函数名大写,可以定义自己的内部状态state,通过this.props对象来接参数。不可以直接修改this.props中的值。
class Welcome extends React.Component {
constructor(props) { // 类构造器,用于继承父类的属性和方法以及定义组件内部的私有状态,在组件内使用时用this.state.来访问
super(props);
this.state = {date: new Date()};
// 注意state的修改是异步的,如果要保证修改数据后再执行某操作,则this.setState({ pageNum: 1 }, this.fetchDataList);
}
// 添加生命周期方法(初始化阶段、挂在阶段、更新阶段、卸载阶段)
componentWillReceiveProps(nextProps, nextContent) {}
// 组件内方法使用,一种是箭头函数的使用,直接this调用。一种是普通函数的定义,但需要在构造函数中bind绑定this
render() { // 渲染函数,返回页面的渲染内容
return <h1>Hello, {this.props.name}</h1>;
}
}
closeModal = () => {
this.uploadTemp(); // 书写逻辑
}
// 2、函数组件(本质是js函数)
// 函数组件中不能定义状态和生命周期方法(因为这些继承自React.Component)
function Welcome(props) { // props是参数对象
// 直接写函数组件的事件处理函数
function handle(){
// 写处理逻辑
}
return <h1>Hello, {props.name}</h1>;
}
// 特点函数名大写,调用函数组件时传的参数通过属性传,在函数子组件的内部通过props.属性名调用 且函数组件内不能定义自己的状态state。不能修改props的值
⚠️:props不是光指调用函数组件或类组件时通过属性传的参数,还有全局引入的及自己定义的初始化属性
1、调用组件时属性传参数
2、自己组件定义的默认属性 .defaultProps={}
3、全局管理的数据引入组件使用的 connect(全局数据)(组件)
生命周期详解
(1)初始化阶段:对类设置默认的属性defaultProps和类型propType及constructor 中的内容,在 constructor 中进行 state、props 的初始化,在这个阶段修改 state,不会执行更新阶段的生命周期,可以直接对 state 赋值。该阶段是在render前进行的
(2)挂在阶段
挂在前的生命周期即render前执行:componentWillMount,一般在此声明周期中进行接口的请求,定时器等更新组件中的状态,用于渲染页面(此时可以取到this.state中值)
挂在阶段:render函数(也不可以setState)
挂在后执行的生命周期:componentDidMount 也可以用于接口的请求,因为react中会实时更新变更的内容(第一次render后调用)
(3)更新阶段:(由于父组件传的props变更或者自己组件的状态state变更引起)
props变更后:
componentWillReceiveProps(一般用于监听props的变化),当时state变化引起时没有此声明周期函数(直接下一个开始)
shouldComponentUpdate(这个生命周期函数返回布尔值,表示要不要更新组件,一般用于做性能优化,因为每次更新都会频繁调用render生成新的虚拟dom,比较耗时,而这个方法在render前执行,返回布尔要不要执行后面的生命周期过程)
componentWillUpdate(这个阶段不可以setState,会导致循环调用)
render
componentDidUpdate(更新后调用的声明周期函数,可以拿到更新前的值)
(4)卸载阶段:componentWillUnmount(组件卸载前的调用,一般执行清理定时器等的操作)
componentWillUnmount() {
clearInterval(this.timer);
}
补充ref转发知识(允许组件接受ref并向下转发)
何时使用refs:管理焦点(文本选择、媒体播放等)、触发强制动画、集成第三方dom库
⚠️ 不能在函数组件上直接使用ref,因为其没有实例
// ref转发的使用方式(这里只介绍转发类组件)
// 1、方式1(React.createRef()创建ref的值)
this.myRef = React.createRef();
return <div ref={this.myRef} />;
访问:this.myRef.current;
// 2、方式2 回调refs(项目中通常使用的方式)
this.camera=null 或{} // 初值的设置
<input
type="text"
ref={(camera) => {
this.camera = camera;
}}
/>
this.camera.focus(); // 通过它获取组件实例及dom
// 3、方式3 String 类型的 Refs(过时的API)
ref=‘字符串’
this.$refs.字符串 获取实例和方法
二、数据流Mobx:React 和 MobX 是一对强力组合。React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新应用状态供 React 使用(不用所有的数据都需要setState来更改),一般用mobx结合使用,而不用redux
== 学习网址:https://cn.mobx.js.org/ 为了解决通信问题 ==
(1)@observer 给类组件增加可观察模式
(2)@observable loading = false; 用于定义动态变更的数据
(3)@computed get searchJS() { // 类似计算属性,一般用于动态拿复杂的数据,即转换成js数据
return toJS(this. loading);
}
(4)@action.bound 给方法进行绑定
(5)toJS() 将数据转位js数据
⚠️ 还有别的数据流方案,例如Redux等
常用的方案:1)Mobx 2) Redux 3) DvaJS 将路由和数据结合
三、路由:保持 UI 与 URL 同步
https://react-guide.github.io/react-router-cn/ (React Router中文文档)
React中使用 React-Router
// 安装 npm install react-router
// 使用 ES6 的转译器,如 babel
import { Router, Route, Link,Redirect } from 'react-router' // 常用组件
// 不使用 ES6 的转译器
var ReactRouter = require('react-router')
var Router = ReactRouter.Router
var Route = ReactRouter.Route
var Link = ReactRouter.Link
// 常用方法:api文档https://react-guide.github.io/react-router-cn/docs/API.html
(1)Router 路由组件 (是React的一个组件,一个容器。真正的路由通过Route组件定义 <Route path="/" component={App}/>规定路径组件关系)
(2)Route 组件的配置
(3)Link
(4)Redirect 设重定向
// 使用(通常在跟组件中将路由配置渲染在 跟组件中,建立起项目中组件与路径的关系,根据浏览器url的路径,对象将对应的组件渲染至绑定的html盒子中)
// routeList路由配置数组 [{routename: "/attendance-manage/result", component:''}]同vue中routes的配置注入Router
const routeList = [
{
path: '/',
component: 'component/app',
routes: [
{
path: '/asd',
component: 'component/topics',
routes: [
{
path: '/asd/login',
component: 'component/home'
}
]
}
]
}
]
export default routeList
<Provider store={store}> // 将全局数据注入
<Router history={history}> // history 属性用于监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router匹配
<div>
{
routeList.map(route =>
<Route
path={route.routename} // 可以匹配动态属性也可以通配符 this.props.params. 取动态参数
key={route.routename}
exact
strict
component={route.component}
/>)
}
</div>
</Router>
</Provider>
// 嵌套路由
// 加载/repos时,会先加载App组件,然后在它的内部再加载Repos组件,但在app组件内部需使用{this.props.children} 表示子组件
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Route>
</Router>
// 路由的跳转
// 1、Redirect 从某一路由跳转至另一路由
<Redirect from=/a" to="/b" />
// 2、 重定向路由
<IndexRedirect to="/welcome" />
// 3、Link 类似a标签,to属性匹配跳转的路径
<Link to="/about">
// 4、导航路由方法,类似vue-router的router.push()等方法
import { browserHistory } from 'react-router';
browserHistory.push('/some/path'); // 参数是路径path
// 路由的钩子(每个路由在离开及进入时都有钩子)
<Route
path="messages/:id"
onEnter={ // 进入时调用,一般用于认证等操作
({params}, replace) => replace(`/messages/${params.id}`)
}
/>
// react-router不同于vue-router提供了很多导航守卫的方法,塔希望自己去实现,例如通过生命周期函数通过浏览器的url(location属性)
React中使用 react-router-dom路由
react-router和react-router-dom两个包都可以实现单页面应用的页面间的跳转
// react-router-dom的使用(基于react-router,不需要重复再引入react-router,会自动在项目中安装,加入了一些功能及组件,部分组件还是直接引的react-router)
React中使用React-router-redux:一般React-Redux和React-Router 都单独使用,除非想通过触发action来变更url
react-router-redux 将react-router 和 redux 集成到一起,用redux的方式去操作react-router(用dispatch触发的方式)
例如,react-router 中跳转需要调用 router.push(path),集成了react-router-redux 就可以通过dispatch的方式使用router,例如跳转可以这样做 store.dispatch(push(url))。本质上,是把react-router自己维护的状态,例如location、history、path等等,也交给redux管理。
import { push } from ‘react-router-redux’
// http://w1.opc.dev.wormpex.com/hr?#/algorithmSchedulingManagement/baseWorkitemManagement
// 例如:store.dispatch(push(‘/algorithmSchedulingManagement/baseWorkitemManagement’));
四、Redux:状态管理(有些项目不使用和mobx的结合,使用Redux进行集中数据的管理)
学习网址:https://www.redux.org.cn/docs/basics/UsageWithReact.html 为了解决组件通信问题
1、知识点:3大原则 单一数据源(唯一一个store中)、state只读(改变state只能触发action)、使用纯函数修改state
触发action(对象或函数,一般用于接口数据的请求),执行对应的reducer更新state, 将容器组件与展示组件相关联(从而在容器组件中使用redux)
// 1、在创建容器组件前,首先要定义mapStateToProps函数,规定如何将store的state数据映射到展示组件的props中(函数名字可以任意取)
const mapStateToProps = state => { // 此处的state是store中的全局数据
return {
todos: state.todos // 组件中使用直接 this.props.todos
}
}
const mapDispatchToProps = dispatch => {
return { // 返回触发的方法。例如
onTodoClick: id => {
dispatch(toggleTodo(id)) // 触发action,dispatch参数可以是对象也可以是函数返回的action对象 dispatch({type:, 其他}) 该对象会对应的reducer的第二参数,组件中使用:this.props.onTodoClick()
}
}
}
// 2、使用 connect()创建容器组件
export default connect(
mapStateToProps,
mapDispatchToProps
})
)(展示组件;
(1)action 与 action 创建函数
// action 只是描述有事情发生这一状态,并没有更新state
// 在 Redux 中的 action 创建函数只是简单的返回一个 action对象
export function addTodo(payload) {
// 可以接口请求,将数据通过payload传出去,可以自己定义名字
return { // 对应的reducer函数的第二参就是这个对象,reducer就是用来更新数据的
type: ADD_TODO,
payload
}
}
// 同步action一般是指通过用户操作自己更新。异步action是接口请求更新数据。
(2)Reducer 函数
// 指定了应用状态的变化如何响应 actions 并发送到 store 的(reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state)
// 用于更新state,纯函数
(previousState, action) => newState
或
[actions.FACTOR_TYPELIST]: (state, action) => action.payload,
// reducer 函数拆分,可以将其拆分,子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象
import { combineReducers } from 'redux' // 将reducer的各个更新合在一起(整合一起,注入store中)
combineReducers({
rbac, // reducer文件夹,它将内部定义的reducer函数整合后导出
project,
user,
dimission,
});
(3)Store,Redux 应用只有一个单一的 store
// 仓库,负责存放状态(state),将 actions 与 reducers 联系起来的东西。
维持应用的 state;
提供 getState() 方法获取 state; // 可获取初始化状态
提供 dispatch(action) 方法更新 state;// 触发 action store.dispatch(addTodo('Learn about actions')),可以在任何触发action
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器。
// 使用完整的实例
// store
import { createStore,combineReducers } from 'redux'
import { Provider } from 'react-redux'; // 用于将Redux的数据绑定到react
import reducers from './reducers' // 各个reducer函数,用于更新对应的state,从而更新视图层
// 导出全局的store,注入main.js中供全局使用,将reducers注入store使得自动触发对应的reducer更新state
export default createStore(
combineReducers({
...reducers,
}),
)
// 将store注入 跟组件 实例中,让所有容器组件都可以访问 store。Provider包装器,用于将store中的数据注入组件
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
五、DvaJS
数据流DvaJS:https://dvajs.com/api/
介绍:dva = React-Router (路由)+ Redux(数据流) + Redux-saga(异步操作)
State:一个对象,保存整个应用状态
View:React 组件构成的视图层
Action:一个对象,描述事件
connect 方法:一个函数,绑定 State 到 View
dispatch 方法:一个函数,发送 Action 到 State
拓展常使用的js库或组件库:
1、react生成二维码的插件
npm install qrcode.react
import QRCode from 'qrcode.react';
React.render(
<QRCode value="http://facebook.github.io/react/" />, // 设该插件组件的value值即可
mountNode
);
// 原生办法利用img标签的src属性
// 下载二维码原理(在文档中动态添加dom元素,手动触发事件,结束后移除添加的dom)
const link = document.createElement('a');
link.style.display = 'none';
link.download = `${name}.png`;
// 部分浏览器不添加到DOM中不会触发click事件
document.body.appendChild(link);
link.href = dataUrl;
link.click();
document.body.removeChild(link);
2、同vue项目一样 也会引入事件处理库mament 处理url的库 qs等
3、组件库 Ant ( https://3x.ant.design/components/select-cn/)
// 案例学会自己封装一个上传组件,一次只能上传一个文件,显示的文件的列表也只能是一个(因为该组件库提供的组件满足不了当要求一次上传一个文件进行异步处理)
// 存在问题:当进行手动上传处理异步任务时,需要拖拽触发自动上传,拿到后端返回的处理url再手动上传。但是这样的话虽然能控制一次只能上传一个,但是可以一直上传,点手动时处理的是最后一个上传成功的文件,此时列表文件确实多个,容易造成困扰。且删除后文件后手动上传参数还存在。在移除时清除url不知道是哪个且会失效(我上传一个Excel 然后删除最后一个上传 删除后点处理 还是会请求后端且参数是我删除的那个excel)
// 期望:文件列表块只能上传一个文件,如果想继续上传则进行手动上传或移除当前文件列表才可以
// 解决思路:自己写一个文件列表的维护及展示文件名和移除按钮及移除方法
this.state = {
fileList: [], // 文件列表
fileName: '', // 文件名字
disabled: false, // 上传框是否禁用
spin: false,
};
this.file = null;
<div className={styles.draggerWrap}>
<Dragger
name 与data时需要时设置
ref={this.refFunc}
multiple: false,
disabled: this.state.disabled,
showUploadList: true, // 是否展示文件列表
action: url, // 上传的地址
beforeUpload: this.beforeUpload, // 上传文件前的校验,一般校验文件的类型和大小
onChange: this.onChange, // 文件状态改变时调用的函数
fileList={this.state.fileList} // 它是一个受控的属性
>
<p className="ant-upload-drag-icon">
<Icon type="upload" />
</p>
<p className="ant-upload-text">点击或将文件拖拽到这里上传</p>
<p className="ant-upload-hint">支持扩展名: {props.accept}</p> // 可以写死也可以动态传参数
</Dragger>
<Spin size="large" spinning={this.state.spin} className={styles.spin} />
{// 实现文件展示及删除和手动上传的功能,如果用户没有选这一块则不展示下方的模块
this.state.fileName !== '' &&
<div className="ant-upload-list">
<div className="ant-upload-list-item ant-upload-list-item-done margin-bottom-middle">
<div className="ant-upload-list-item-info">
<span>
<i className="anticon anticon-paper-clip" />
<span className="ant-upload-list-item-name" title="ceshi.png">{this.state.fileName}</span>
</span>
</div>
<i title="删除文件" className="anticon anticon-cross" onClick={this.removeFile} />
</div>
<Button type="primary" htmlType="submit" onClick={this.onSubmit}>
确认提交
</Button>
</div>
}
</div>
// 文件上传之前进行格式校验
beforeUpload(file) {
this.setState({
fileList: [],
fileName: file.name,
disabled: true,
spin: false,
});
if (file) {
const { name, size } = file;
const fileFormat = util.getFileNameFormat(name);
const arr = ['xls', 'xlsx'];
const mb = size / 1024 / 1024;
if (mb > 10) {
this.$message.warning('文件大小不能超过10MB');
return false;
}
if (!arr.includes(fileFormat)) {
this.$message.warning('只能上传.xls或.xlsx格式的文件');
return false;
}
}
return true;
},
onChange = (info) => { // 存文件的列表和更改禁用状态
this.setState({
fileList: info.fileList,
});
const status = info.file.status;
switch (status) {
case 'done':
this.props.uploadInfo.success(info);
this.setState({
disabled: false,
spin: false,
});
break;
case 'error':
message.error('上传失败');
this.setState({
disabled: false,
spin: false,
});
this.props.uploadInfo.error(info);
break;
case 'removed':
this.setState({
disabled: false,
spin: false,
});
break;
default:
this.props.uploadInfo.progress(info);
break;
}
}
removeFile = () => { 移除时将文件清空更改禁用状态
this.setState({
fileName: '',
disabled: false,
});
}
onSubmit = (isHandle) => { // 手动处理成功后清除文件及禁用
if (isHandle === true) {
this.setState({ fileName: '', spin: true });
// const { action, data } = this.props.uploadInfo;
// Request.post(action, );
} else {
if (this.resolve) {
this.setState({
fileName: '',
spin: true,
});
this.resolve();
}
}
}
.draggerWrap {
overflow: hidden;
}
.draggerWrap > span {
height: 200px;
display: block;
// onChange接受的参数对象,里面包含当前操作的文件和文件列表,当前文件的状态有4种 uploading done error removed
{
file: { /* ... */ },
fileList: [ /* ... */ ],
event: { /* ... */ },
}
// 下载模版的实现
// 方法1 直接请求后端的url下载(后端直接给了下载的url)
downloadTemplete() {
const urls = '/roster/api/op/arrange/deleteSchedule/download';
const origin = location.origin; // 会返回协议域名端口号
let url = `${origin}${urls}`;
window.open(url, '_blank', 'noopener');
},
// 方法2 自己拼接请求的url。利用a链接实现(一般针对后端加密返回的二进制这样做)
const downloadFunc = (response, name) => {
const urlLink = window.URL.createObjectURL(new Blob([response])); // 拿到下载文件url,因为有时后端返回的格式直接时二进制的文本
const link = document.createElement('a'); // 创建一个a标签
link.href = urlLink; // 给a标签设置属性href
link.setAttribute('download', name);
document.body.appendChild(link); // 将动态创建的标签添加到文档中,因为有的浏览器不支持手动触发动态创建的标签的click
link.click();
document.body.removeChild(link); // 动态移除该标签
};
// 方式3 后端提供接口返回下载的完整url
在接口的then 方法中
let url = res.data
window.open(url, '_blank', 'noopener');
react版本更新及学习(工作中react项目使用的16.2.0) 15.6-18.2.0
(1)15.6.0 2017/6/13
(2)16.0.0 2018/10/23-2020/10/14
(3)16.1.0
(4)16.2.0
(5)16.3.0-16.10.2
(6)16.11.0 16.12.0 16.13.0 16.14.0
(7)17.0.0|1|2 2020/10/20(2020-2021更新的是17版本)
(8)18.0.0 2022/3/29 2022年后
(9)18.1.0 2022/4/26
(10)18.2.0 2022/6/14
一、例如15 VS 16(生命周期变更、架构更新、hooks钩子)
文章参考资料
react官网:https://zh-hans.reactjs.org/
react技术揭秘:https://react.iamkasong.com/preparation/idea.html#react%E7%90%86%E5%BF%B5
总结:
(1)react16去掉了componentWillMount、componentWillUpdate、componentWillReveiveprops,增加了静态方法 getDerivedStateFromProps、getSnapshotBeforeUpdate这两个方法
1)getSnapshotBrforeUpdate:在render方法之后,componentDidUpdate之前被执行,即真实DOM更新之前(获取更新前的真实DOM和更新前后的State&props信息)。该方法需要一个返回值,作为componentDidUpdate的第三个参数
2)getDerivedStateFromProps:替代componentWillReveiveprops与componentWillMount(实质是替代componentWillReveiveprops)。该方法是根据父组件更新props时触发的,它是静态方法,防止在该方法中调用this实例,不合理使用this.state等导致 死循环等
(2)官方在react16虽然去掉了componentWillMount、componentWillUpdate、componentWillReveiveprops这三个生命周期,但是在react16还是能用,只不过可能会在新版本中产生bug,尤其对于新增的生命周期。
(3)生命周期迭代
16.3:为不安全的生命周期引入别名,UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都可以在此版本中使用。)
16.9:为 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 启用废弃告警。(旧的生命周期名称和新的别名都将在这个版本中工作,但是旧的名称在开发模式下会产生一个警告。)
17.0:删除 componentWillMount、componentWillReceiveProps 和 componentWillUpdate。(在此版本之后,只有新的 “UNSAFE_” 生命周期名称可以使用。)
2、架构:会使原始的同步渲染变成异步(对React核心算法做一次重写)
(1)react15可分为两层:交替同步执行
Reconciler协调器:通过reconcile(diff算法)递归计算哪些组件需要更新,并通知Renderer。他是递归处理的
// 有更新时工作原理(this.setState、this.forceUpdate、ReactDOM.render等触发更新):
调用函数组件、或class组件的render方法,将新返回的JSX转化为虚拟DOM
将虚拟DOM和上次更新时的虚拟DOM对比
通过对比找出本次更新中变化的虚拟DOM
通知Renderer将变化的虚拟DOM渲染到页面上
Renderer渲染器:将更新的组件渲染到真实dom上(渲染到页面 )。不同平台有不同的Renderer
(1)ReactDOM 负责在浏览器环境渲染的Renderer
(2)ReactNative 渲染APP原生组件
(3)ReactTest 渲染出纯JS对象用于测试
(4)ReactArt渲染到Canvas,svg或者vml(ie8)
React15问题:CPU和IO问题(网络延迟)
CPU问题(遇到大计算量的操作或设备性能不足使页面掉帧,导致卡顿):
每16.6ms浏览器会刷新一次,而渲染到屏幕上浏览器要进行JS的执行-样式布局-样式绘制,如果在这一帧内这三个过程不能全部完成,就会出现页面掉帧,也就是页面卡顿的情况(JS脚本执行和浏览器布局、绘制不能同时执行.16.6ms内需完成 js脚本执行–样式布局–样式绘制)
IO问题:
网络延迟是前端开发无法解决的情况,但如何在网络延迟的情况下,减少用户对网络延迟的感知
解决:将同步的更新变为异步的、可中断的更新
(2)react16:架构可以分为3层
Scheduler调度器:调度任务的优先级,高优任务优先进入Reconciler(协调器)
Reconciler协调器:负责找出变化的组件
Renderer渲染器:负责将变化的组件渲染到页面上(Renderer根据Reconciler为虚拟DOM打的标记,同步执行对应的DOM操作)
(3)变化:
新增Scheduler调度器
Reconciler变化:内部采用Filber架构,更新过程是可中断的循环过程,根据浏览器是否有剩余时间来判断,它不再是交替执行,而是在内存中执行只有所有组件完成Reconciler才算完成
React16整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer
其中红框中的步骤随时可能由于以下原因被中断:
有其他更高优任务需要先更新
当前帧没有剩余时间
3、更新机制
react15更新流程:协调器和渲染器依次执行工作,整个过程都是同步的(带来cpu及io问题)
react16更新流程:如果协调器中有任务正在 diff,但是调度器有更高优的任务进来,那刚才的任务就会中断执行,反而先执行高优的任务,但由于调度器和协调器都是在内存中工作的,所以即使有中断发生,用户也不会看到更新不完全的视图(减少用户卡顿等的感知)
16各版本迭代
React16.0:新的架构Fiber发布
React16.3:新的生命周期发布,为不安全的生命周期引入别名
React16.8:hook发布
React16.9:为不安全的生命周期启用废弃告警
React16.13.0 新的警告(Render期间更新的警告、冲突样式规则警告、废弃字符串ref的警告)
4、react hooks(钩子) 组件尽量写成纯函数
版本:react >= 16.8, React Native >= 0.59 版本后开始支持hooks,目前项目中升级RN后React Native为0.63,react版本为16.13.1
hooks的作用:在不编写class的情况下使用state及其他react特性(因为真实的React APP由多个类安装层级构成,复杂度高,引入Redux后更难拆分、重构、测试,所以希望react还是只是简单的数据管道而不是复杂的容器,而函数组件又是无状态无生命周期解决不了)
hooks优点:
1,函数组件可以拥有自己的状态、并且可以监听生命周期
2,代码编写、复用及管理变得简单
(1)useState:状态钩子,纯函数不能有状态,把状态放入钩子中让纯函数有状态
// 状态钩子例子(返回数组,第一个是变量,第二个是函数更新状态)
import React, { useState } from "react";
export default function Button() {
const [buttonText, setButtonText] = useState("Click me, please");
function handleClick() {
return setButtonText("Thanks, been clicked!");
}
return <button onClick={handleClick}>{buttonText}</button>;
}
(2)useContext:共享状态钩子,用于在组件之间共享状态
// 例如需要在组件Navbar 和 Messages共享状态,在组件外部建立Context
const AppContext = React.createContext({});
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar/>
<Messages/>
</div>
</AppContext.Provider>
// AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享
// 在子组件的使用,通过useContext()获取
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
(3)useReducer():action 钩子用来引入 Reducer 功能
const [state, dispatch] = useReducer(reducer, initialState);//它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数
// 自己的reducer函数
const myReducer = (state, action) => {
switch(action.type) {
case('countUp'):
return {
...state,
count: state.count + 1
}
default:
return state;
}
}
// 在组件中的使用
function App() {
const [state, dispatch] = useReducer(myReducer, { count: 0 });
return (
<div className="App">
<button onClick={() => dispatch({ type: 'countUp' })}>
+1
</button>
<p>Count: {state.count}</p>
</div>
);
}
(4)useEffect():副作用钩子,用于向服务器请求数据(以前放在componentDidMount中代码)
useEffect(() => {
// Async Action
}, [dependencies])
// 第一个参数是函数,用于异步操作,第二个参数数组是依赖项,该数组发生变化时触发,不写第二参表示每次组件渲染时就会执行 https://juejin.cn/post/7083230365027926053
const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId])
if (loading === true) {
return <p>Loading ...</p>
}
return <div>
<p>You're viewing: {person.name}</p>
<p>Height: {person.height}</p>
<p>Mass: {person.mass}</p>
</div>
}
// 相当于componentDidMount,componentDidUpdate 和 componentWillUnmount
import React, { useEffect } from 'react;
function Example() {
useEffect(() => {
// 需要在componentDidMount,componentDidUpdate执行的副作用
return () => {
// 需要在componentWillUnmount清除的副作用
}
}, [])
}
注:当父组件重渲染次数很多时,考虑使用useMemo和useCallback
useMemo和useCallback都是通过缓存来防止重复计算和re-render,区别在于useMemo缓存的是值,useCallback缓存的是函数
性能优化之useMemo、useCallback:https://juejin.cn/post/7010278471473594404#heading-7
如何正确使用useMemo和useCallback:https://juejin.cn/post/7122027852492439565
二、react16 Vs react17(重点17版本更新了什么)事件委托、全新JSX、渐进式升级
1、更新事件委托(document 切换为 root、onScroll不再冒泡、去除事件池):不会将事件处理器添加到document上而是添加到渲染react树的根dom容器中
2、react17中使用了不同的机制生成组件调用栈,该机制与常规的原生js调用栈缝合在一起,可以在生产环境中获得完全符号化的 react组件调用栈信息
3、渐进式升级:不用一次性升级整个应用(可以同时使用react17|18)
4、全新的JSX转换 之前是React.createElement(…)还支持,自动从 React 的 package 中引入新的入口函数并调用
三、react17Vs react18(重点18版本更新了什么。17到18升级会遇到的问题及新的功能,解决并发问题,但策略是渐进升级,直接升级也不会有任何破坏性影响)
⚠️:并发渲染是底层的一次 架构设计升级,React18关注点在于更快的性能以及用户交互响应效率,其实设计理念处处包含了中断和抢占的概念
概述:渐进式(过渡,紧急非紧急更新)、setState同步|异步(自动批处理)、并发特性(并发渲染机制,多个版本react)(使用concurrent mode开启并发特性)
1、react18废弃会IE浏览器的支持
2、createRoot:ReactDOM.render会触发警告,使用New root api:createRoot
// 17版
import ReactDOM from ‘react-dom’;
import App from 'App';
ReactDOM.render(<App />, document.getElementById('root'));
// 18版(可以为一个react app 创建多个跟节点)
import ReactDOM from ‘react-dom’;
import App from 'App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
3、setState 同步/异步
react18将将多个状态更新合并为一次重新渲染,以获得更好的性能,在18之前React 只能在组件的生命周期函数或者合成事件函数中进行批处理,Promise、setTimeout异步函数以及原生事件中是不会对其进行批处理的。但18后所有的更新都将自动批处理,除非自己设置await等让多次执行render
// react17 setState
(1)生命周期钩子函数及合成事件中:无论调用多少次setState都不会立马更新,最终执行一次render(异步,不是一个更新完再一个 )
合成事件指有自身的绑定机制,react中经常书写函数的方法都是合成事件
(2)异步函数与原生事件中:setState本身并不是异步的,但异步函数及原生事件中,在调用setState时若react正处于更新过程则等更新完再执行(同步的,等上一个更新完再更新新的,同步完成)
// 如何在18中退出批处理,可以使用提供的方法flushSync
function handleClick() {
flushSync(() => {// 他会以函数为作用域,内部的多个setState仍然批量处理
setCount(3);
});
// 会在 setCount 并 render 之后再执行 setFlag,这样就执行两次render
setFlag(true);
}
function handleClick() {
flushSync(() => {
setCount(3);
setFlag(true);
});
// setCount 和 setFlag 为批量更新,结束后
setLoading(false);
// 此方法会触发两次 render
}
4、新api
// 1、useSyncExternalStore 订阅外部数据源
// 2、useInsertionEffect
// 3、startTransition // 非紧急更新
// 4、useTransition
// 5、useDeferredValue
总结:
React18.0:开启concurrent Mode
(1)使用concurrent mode,开启并发特性(不同版本react)
(2)自动批处理(setState)
(3)新功能:Transition(用于区分紧急和非紧急更新),由此带来新的钩子startTransition和useTransition
react18使用:https://juejin.cn/post/6973222013028532237#heading-1
详细链接:https://mp.weixin.qq.com/s?__biz=Mzk0MDMwMzQyOA==&mid=2247490023&idx=1&sn=0ca662132f4f44ef61f608dcb095f1fa&source=41#wechat_redirect