react 环境搭建
- npm 已经内置了 react 的脚手架(脚手架:用于搭建环境的工具), node >= 8.10 npm >= 5.6
创建 react 项目
- npx create-react-app 项目名
组件
- js 文件就是组件
- 组件的写法
- 函数式组件,一个函数返回 html 节点,只能返回一个根节点
- class 组件
- 函数组件 一个函数返回 html 节点, 只能返回一个节点无 state 组件
jsx 语法
- 在 js 内写 html,如果要在该 html 写 js 的话需要使用 {}
- 标签的 class 换成 className
- 标签的 for 换成 htmlFor
props
- 需要下载 prop-types 包
npm i prop-types
- 需要引入 import PropTypes from ‘prop-types’;
- static propTypes 设置 prop 的规则
static propTypes = {
// 设置 name 必须为字符型,而且必传
name: PropTypes.string.isRequired,
}
- static defaultProps 设置 prop 的默认值
static defaultProps = {
// 设置 sex 默认是 '男'
sex: '男'
}
state
- 调用 let { count } = this.state;
- 修改
this.setState({ count: count - 1, });
条件渲染
- 利用样式显示隐藏 box
<div className={show ? 'box' : 'box none'}></div>
// 在 jsx 内的 style 属性要写成对象类型
<div style={{display:show ? 'block' : 'none'}} className="box"></div>
- dom 出现和消失
{show ? <div className="box"></div>:''}
// 或 && 写法
{show&&<div className="box"></div>}
组件的生命周期
生命周期函数不写成箭头函数也可以使用 this
- 挂载(首次加载)
- constructor()
- 必须有 super()
- 如果需要 state 也需要写在 constructor 里面
- 禁止使用 setState
- props 的值不可以复制给 state
- static getDerivedstateFromProps()
- 基本不用
- render() 组件中必须的方法
- 纯函数,不可以加上异步操作
- componentDidMount() 一般使用这个
- 组件 DOM 渲染完毕,可以发送网络请求,setState 更新数据
- constructor()
- 更新(组件的 state 和 props 发生改变)
- shouldComponentUpdate(nextProps,nextState) 接收两个参数,新的 props 和新的 state 组件是否应该可以更新,需要返回一个布尔值,用的不多
- render() 根据 state 和 props 渲染 react 节点,发生改变重新渲染
- componentDidUpdata() 组件更新 dom 渲染完毕,可以发送网络请求使用 setState 更新数据,获取更新后的真实 dom 节点
- 卸载(组件 dom 消失)
- componentWillUnmount() 组件即将被销毁时触发,清除定时器等
refs 的使用
- createRef() 容器
- 类里面定义一个属性, 属性的值是 createRef()
- 将这个值绑定到任一元素的 ref 属性上,那么当你访问这个属性下的 current 属性是就拿到了真实 dom
// 上面写
p = createRef()
// dom 节点写
<p ref={this.p}>我是一个段落</p>
- 将真实 dom 赋值给 react 的属性
- ref 属性的属性值写一个函数,该函数默认接收的第一个参数就是真实 dom 将这个真实 dom 赋值给类的属性即可
// 上面写 也可以省略 inputRef = null // dom 节点写 <input type="text" ref={(ele)=>this.inputRef=ele} />
虚拟 dom 中 key 的使用
- 经典面试题:
- react/vue中的key有什么作用?(key的内部原理是什么? )
- 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
- 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到到页面
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 为什么遍历列表时,key最好不要用index?
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新==〉界面效果没问题,但效率低。 - 如果结构中还包含输入类的DOM:
会产生错误DOM更新==>界面有问题。 - 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
react脚手架配置代理
方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
-
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
-
编写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': ''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
发布订阅模式
PubSub 下载
npm i pubsub-js
- 最好在 componentDidMount 设置订阅 并且在 componentWillUnmount 取消订阅
PubSub.unsubscribe(this.token)
- 接收数据端的使用 PubSub.subscribe(‘订阅名’, (msg, stateObj) => {函数体})
- 发送数据端的使用 PubSub.publish(‘订阅名’,{ 发送的数据 })
Fragment 标签
- 一个语法糖
- 空标签不会渲染 也可以简写成 <></>
- 可循环,可以加 key
strictMode 严格模式
- 使用 <React.StrictMode></React.StrictMode>包裹可以使用严格模式
- 有助于
- 识别不安全的生命周期
- 关于使用过时字符串ref API的警告
- 关于使用废弃的findDOMNode方法的警告
- 检测意外的副作用
- 检测过时的context API
createPortal
- 需要 import { createPortal } from ‘react-dom’
- 可以将组件渲染到 app 以外的地方
render() {
return createPortal(
<div>
<h2>我是对话框</h2>
</div>,
document.querySelector('body')
)
}
Context 共享数据
- 父组件写
import { Component, createContext } from 'react'; // 这里可以导入子组件 // 导入子组件之后先导出 provide 和 consumer // 创建了一个 themeContext 默认值是 red export const { Provider, Consumer } = createContext('red') // 父组件传递的数据需要使用 provider 包裹 value 的值就是传递的值,必须传,会覆盖上面的 red ,上面可以不传 <Provider value='red'> <Context1 /> </Provider>
- 子组件写
//先从父组件导入 Consumer import { Consumer } from './ContextDemo' // 使用 父组件的数据的时候 需要使用 consumer 包裹 里面需要是个函数 <Consumer> { (x) => ( <div> <h4>我是子组件{x}</h4> <Context11 /> </div> ) } </Consumer>
- 有的时候很深层次的后代组件想要使用祖先组件的 state 但是中间的组件不需要使用,我们可以在祖先组件定义一个变量,这样就可以使用了,就不用层层向下传递了
const getUser=(<User user={user} />)
render props类似作用域插槽
- 父组件写 {this.props.render(x,y)} 意思为传递 x和y 给子组件
- <MouseTracker render={(x,y)=>}/> 父组件采用这种方法调用子组件,子组件接收了 x 和 y
react router 路由
- 安装 npm i react-router-dom
路由的使用
需要先导入 import {BrowserRouter, Route } from ‘react-router-dom’
-
BrowserRouter 是一个组件,作用是只有在用这个组件包裹的地方才能使用路由相关的技术
- 一般使用 BrowserRouter 包裹全部的 render
- 也可以导入 HashRouter,HashRouter 和 BrowserRouter 一样,只是前者是锚点模式,后者是 历史记录模式
-
Route 是一个组件,该组件的作用是根据传递的地址展示传递的组件,就是一个页面
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由 -
嵌套路由
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的 -
添加 Switch 使用 Switch 包裹的组件只展示第一个匹配成功的组件
-
添加 Link 实现路由跳转,写法 home
- 也可以添加 NavLink 和 Link 基本没有区别, NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
-
添加 withRouter 让非路由组件可以获取路由数据
- 使用方法,在导出组件时用 withRouter 包裹一下 withRouter(App);
- 但是这个组件需要 BrowserRouter 包裹
-
Redirect的使用
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
- 添加 Redirect 路由重定向
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
路由组件与一般组件
1.写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的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"
解决多级路径刷新页面样式丢失的问题
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter
向路由组件传递参数
1.params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:this.props.match.params
2.search参数
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数
无痕模式
- 路由模式有 push 模式和 replace 模式,默认是 push 模式,也就是路由跳转会有记录
- 如果在跳转也就是 Link 标签添加 replace 就会变成无痕模式,也就不会产生记录
编程式路由导航
- 借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- this.prosp.history.push()
- this.prosp.history.replace()
- 如果传递参照,可以传递 params search state 参数,接收和获取对应写就行
- this.prosp.history.goBack()
- this.prosp.history.goForward()
- this.prosp.history.go(n)
- n 为几就前进或者后退几步,正为前进负为后退
BrowserRouter与HashRouter的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。
Ant Design
npm i antd --save
使用
- 使用时需要引入
import { Button } from 'antd'
和import 'antd/dist/antd.css'
- 如果使用图标需要
import {想要使用的图标} from '@ant-design/icons'
antd的按需引入+自定主题
- 安装依赖:npm i react-app-rewired customize-cra babel-plugin-import less less-loader@7.1.0
- 修改package.json
…
“scripts”: {
“start”: “react-app-rewired start”,
“build”: “react-app-rewired build”,
“test”: “react-app-rewired test”,
“eject”: “react-scripts eject”
},
… - 根目录下创建config-overrides.js
// 配置具体的修改规则
const { override, fixBabelImports,addLessLoader} = require(‘customize-cra’);
module.exports = override(
fixBabelImports(‘import’, {
libraryName: ‘antd’,
libraryDirectory: ‘es’,
style: true,
}),
addLessLoader({
lessOptions:{
javascriptEnabled: true,
modifyVars: { ‘@primary-color’: ‘green’ },
}
}),
); - 备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css’应该删掉
自定义 icon
- 利用阿里图标制作一个自定义的 icon
- 先引入 import Icon from ‘@ant-design/icons’
- 定义一个函数 const iosIcon = () => (svg图片代码)
- 在定义一个函数 const IsoIcon = (props) => <Icon component={iosIcon} {…props} />
- 直接在后面引就可以了
压缩 avg
- 全局安装 npm i -g svgo
- svgo 图片名
Redux
安装
npm i redux
一些 API
- store.getState() 获取 store 的状态
- store.dispatch({ type: ‘decrement’, data: value * 1 }) 执行 redux 的函数
- store.subscribe(()=>{函数体}) 当 store 数据改变时执行回调
使用
-
.src下建立:
-redux
-store.js
-count_reducer.js 加工数据
-count_action.js 专门用于创建action对象
-constant.js 放置容易写错的type值 -
store.js:
- 引入redux中的createStore函数,创建一个store
- createStore调用时要传入一个为其服务的reducer
- 记得暴露store对象
import { createStore } from "redux"; import countReducer from './count_reducer' export default createStore(countReducer)
-
count_reducer.js:
- reducer的本质是一个函数,接收:preState(上次的 state 默认是 undefined 一般需要给一个默认值), action,返回加工后的状态
- reducer有两个作用:初始化状态,加工状态
import {INCREMENT,DECREMENT} from './constant' const initState = 0 //初始化状态 export default function countReducer(preState=initState,action){ //从action对象中获取:type、data const {type,data} = action //根据type决定如何加工数据 switch (type) { case INCREMENT: //如果是加 return preState + data case DECREMENT: //若果是减 return preState - data default: return preState } }
-
count_action.js
- 该文件专门为Count组件生成action对象
import {INCREMENT,DECREMENT} from './constant' export const createIncrementAction = data => ({type:INCREMENT,data}) export const createDecrementAction = data => ({type:DECREMENT,data})
-
constant.js
- 该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
- 这样引入之后就会有提示,就不容易写错了
export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
-
在index.js中监测store中状态的改变,一旦发生改变重新渲染,原来渲染 App 的 render 不要删除
ReactDOM.render(<App />,document.getElementById('root')); store.subscribe(()=>{ ReactDOM.render(<App />,document.getElementById('root')); })
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
- 真正在项目中调用 redux 使用
store.dispatch(createIncrementAction(value))
就可以了
异步 action
- 明确:延迟的动作不想交给组件自身,想交给action
- 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
- 具体编码:
npm i redux-thunk
,并配置在store中import { createStore, applyMiddleware } from "redux"; import countReducer from './count_reducer' import thunk from "redux-thunk"; export default createStore(countReducer,applyMiddleware(thunk))
- 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
//异步action中一般都会调用同步action,异步action不是必须要用的。 export const increamentAsyncAction = (data,time) =>{ // dispatch 是从 store 中得到的,因为是 store 调用的所以可以直接获取 return (dispatch)=>{ setTimeout(() => { dispatch(increamentAction(data)) }, time); } }
- 异步任务有结果后,分发一个同步的action去真正操作数据。
- 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
react-redux基本使用
安装
npm i react-redux
- 使用 react-redux 之后就可以将 index.js 中 subscribe 监测语句删除了,因为 react-redux 自己就可以监测了
- 明确两个概念:
- UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
- 容器组件:负责和redux通信,将结果交给UI组件。
- 如何创建一个容器组件————靠 react-redux 的 connect 函数
connect(mapStateToProps,mapDispatchToProps)(UI组件)
-mapStateToProps:映射状态,返回值是一个对象
-mapDispatchToProps:映射操作状态的方法,返回值是一个对象 - 备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
- 备注2:mapDispatchToProps,也可以是一个对象
import { connect } from 'react-redux'
import countUi from '../../components/Count'
import {increamentAction,decreamentAction,increamentAsyncAction} from '../../redux/count_action'
function mapStateToProps(state) {
return { count: state }
}
function mapDispatchToProps(dispatch) {
return {
increment: (data) => {
dispatch(increamentAction(data))
},
decreament: (data) => {
dispatch(decreamentAction(data))
},
increamentAsync: (data,time) => {
dispatch(increamentAsyncAction(data,time))
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(countUi)
简化代码
import { connect } from 'react-redux'
import countUi from '../../components/Count'
import { increamentAction, decreamentAction, increamentAsyncAction } from '../../redux/count_action'
export default connect(
state => ({ count: state }),
dispatch => ({
increment: (data) => {
dispatch(increamentAction(data))
},
decreament: (data) => {
dispatch(decreamentAction(data))
},
increamentAsync: (data, time) => {
dispatch(increamentAsyncAction(data, time))
},
})
)(countUi)
//其中的 第二个参数可以在次简写为
{
increment: increamentAction,
decreament: decreamentAction,
increamentAsync: increamentAsyncAction
}
react-redux优化
- 容器组件和UI组件整合一个文件
- 无需自己给容器组件传递 store,给包裹一个即可。
import store from './redux/store'; import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App /> </Provider> ,document.getElementById('root'));
- 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
- mapDispatchToProps也可以简单的写成一个对象
- 一个组件要和redux“打交道”要经过哪几步?
- 定义好UI组件—不暴露
- 引入connect生成一个容器组件,并暴露,写法如下:
connect(
state => ({key:value}), //映射状态
{key:xxxxxAction} //映射操作状态的方法
)(UI组件) - 在UI组件中通过this.props.xxxxxxx读取和操作状态
组件中使用
- this.props.count 获取 count 的值
- this.props.increment 使用 increment 函数
react-redux 共享多个数据
- 定义两个组件通过redux共享数据。
- 重点:两个组件的 reducer 要使用combineReducers进行合并,
合并后的总状态是一个对象!!! - 交给 store 的是总 reducer ,最后注意在组件中取出状态的时候,记得“取到位”。
react-redux开发者工具的使用
npm i redux-devtools-extension
- store中进行配置
import {composeWithDevTools} from ‘redux-devtools-extension’
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
扩展
1.setState更新状态的2种写法
setState 函数是同步的,但是后期引起 react 状态更新是异步的
(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
import { lazy,Suspense } from 'react';
//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
可以不用必须有一个真实的DOM根标签了
使用
<Fragment><Fragment>
<></>
区别
- Fragment 可以写属性,空标签不可以
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. 打包上线
npm run build
进行打包npm i serve -g
安装工具serve build
运行
8. 错误边界
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
state = {hasError: ''}
// 生命周期函数,一旦后台组件报错,就会触发
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
3.集中式管理:
redux
4.conText:
生产者-消费者模式
新电脑 新系统 开始 vue 项目
- 第一种情况 给你一个 git 仓库地址
- 安装 git 下载 gitbash 安装即可
- 安装 node 官网安装即可
- 安装 vscode 下载插件
- 第二个情况 给一个 demo 从 0 开始
- 安装 node
- 安装脚手架工具
- 命令行工具 gitbash
- 安装 vscode