之前,学习过React,但是2、3个月没用的话,就是忘得差不多了,在最近的这段时间里发现大厂用React技术栈很多,然后就特意去温习了一下,整个感觉就是React挺吃JS的,国庆期间然后卷了一波,成为他们口中所说的“卷王!”
首先需要引入React库,react.development.js
需要在react-dom.development.js
之前引入,因为react.development.js
是核心库。
{}
包起来class
,得用className
style={{color:'red',fontSize:'12px'}}
形式去写如果想要写判断条件,可以写成三元表达式,三元表达式可以嵌套使用
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,下面这些都是表达式
a
a+b
main()
arr.map()
function test(){}
语句代码:下面这些都是语句代码
if(){}
for(){}
switch(){}
注:
1. 组件中render方法中的this为组件实例对象
2. 组件自定义方法中的this为undefined,如何解决?
3. 状态数据,不能直接修改,得通过setState来对数据进行更新
类组件中在创建之后发生了什么?
render()
函数内部定义常量,拿到state中的数据后,return
出需要展示的内容赋值语句+箭头函数的形式
来自定义方法,从而避免this指向丢失的问题最后,采用ReactDOM.render()
的方式,将组件渲染到页面上。
作用:
props的基本使用
在脚手架对props进行限制时,首先需要npm i prop-types
,然后在子组件中引入,
然后需要用static关键字声明,之后对其接收到的数据进行限制
<!DOCTYPE html>
<html lang="en">
<head>
<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="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入React核心库 -->
<script src="../../00-React库/react.development.js"></script>
<!-- 引入React.dom库 -->
<script src="../../00-React库/react-dom.development.js"></script>
<!-- 引入babel -->
<script src="../../00-React库/babel.min.js"></script>
<!-- 引入prop-type,来对props进行限制 -->
<script src="../../00-React库/prop-types.js"></script>
<!-- 需求 -->
<!--
将要展示的年龄在原来的基础上+1
姓名限制为必填
性别限制为字符串
年龄限制为数字
-->
<script type="text/babel">
// 创建组件
class Person extends React.Component{
render(){
console.log(this)
const {name,age,sex} = this.props
// props是只读的,不可以修改
// this.props.name = 'aaa' 此行代码会报错
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age+1}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
// 对标签属性进行类型、必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name为必传项,且为字符串
age:PropTypes.number, //限制age为数字
sex:PropTypes.string, //限制sex为字符串
speak:PropTypes.func, //限制speak为函数
}
// 指定属性的默认值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
// 渲染组件
ReactDOM.render(<Person name="李白" age={18} sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name='杜甫' sex="男"/>,document.getElementById('test2'))
// ReactDOM.render( ,document.getElementById('test3'))
// 批量传递props
const p = {name:'宋庆扬',age:20,sex:'女'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak() {
console.log('我说话了')
}
</script>
</body>
</html>
在该类组件的内部,在需要添加的属性前面加上static
关键字,即可给类自身添加属性
<!DOCTYPE html>
<html lang="en">
<head>
<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="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入React核心库 -->
<script src="../../00-React库/react.development.js"></script>
<!-- 引入React.dom库 -->
<script src="../../00-React库/react-dom.development.js"></script>
<!-- 引入babel -->
<script src="../../00-React库/babel.min.js"></script>
<!-- 引入prop-type,来对props进行限制 -->
<script src="../../00-React库/prop-types.js"></script>
<!-- 需求 -->
<!--
将要展示的年龄在原来的基础上+1
姓名限制为必填
性别限制为字符串
年龄限制为数字
-->
<script type="text/babel">
// 创建组件
class Person extends React.Component {
// 对标签属性进行类型、必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, //限制name为必传项,且为字符串
age: PropTypes.number, //限制age为数字
sex: PropTypes.string, //限制sex为字符串
speak: PropTypes.func, //限制speak为函数
}
// 指定属性的默认值
static defaultProps = {
sex: '男',//sex默认值为男
age: 18 //age默认值为18
}
render() {
console.log(this)
const { name, age, sex } = this.props
// props是只读的,不可以修改
// this.props.name = 'aaa' 此行代码会报错
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age + 1}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
// 渲染组件
ReactDOM.render(<Person name="李白" age={18} sex="男" speak={speak} />, document.getElementById('test1'))
ReactDOM.render(<Person name='杜甫' sex="男" />, document.getElementById('test2'))
// ReactDOM.render( ,document.getElementById('test3'))
// 批量传递props
const p = { name: '宋庆扬', age: 20, sex: '女' }
ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
function speak() {
console.log('我说话了')
}
</script>
</body>
</html>
理解:组件内的标签可以定义ref属性来标识自己
ref直接接收一个回调,该回调的参数就是该元素的节点,随后起一个常量名用来接收该节点。
ref方式数的方式定义的,在更新的过程中它会被执行两次,第一次传入的参数为null,第二次传入的参数才是dom元素。这是因为在每次渲染时会创建一个新的函数实例,React会清空旧的ref然后再设置一个新的。
解决方案
将ref的回调函数定义成一个class的绑定函数的方式可以避免以上问题,但是写成内联函数的方式也无关紧要
this.ref2.current
指代的就是当前ref2所指向的根节点
首先输入类的DOM标签中都包含着一个onChage回调
,然后可以在这个回调中使用setState()
来改变state中的数据,与Vue数据双向绑定原理类似
<body>
<script src="../../00-React库/react.development.js"></script>
<script src="../../00-React库/react-dom.development.js"></script>
<script src="../../00-React库/babel.min.js"></script>
<script src="../../00-React库/prop-types.js"></script>
<div id="box"></div>
<script type="text/babel">
// 1. 创建组件
class Login extends React.Component {
state = {
username:'',
password:''
}
submit = (event)=>{
event.preventDefault();
const {username,password} = this.state
alert(`您输入的用户名是${ username},密码是${ password}`)
}
// 保存用户名到状态中
saveUsername = (event)=>{
this.setState({
username:event.target.value
})
}
// 保存密码到状态中
savePassword = (event)=>{
this.setState({
password:event.target.value
})
}
render() {
return (
<form onSubmit={this.submit}>
用户名:<input type="text" onChange={this.saveUsername} name="username" />
密码:<input type="text" onChange={this.savePassword} name="password" />
<button>提交</button>
</form>
)
}
}
// 2. 渲染组件
ReactDOM.render(<Login/>,document.getElementById('box'))
</script>
</body>
旧的生命周期
卸载组件的方法为:ReactDOM.unmountComponentAtNode(document.getElementById('box'))}
组件挂载时,按照图中的顺序依次执行
当组件更新时
shouldComponentUpdate默认返回true
,它是组件更新的总开关,如果它返回的是false,组件将无法更新,那么之后的回调也将无法继续执行,程序只能执行到阀门这块forceUpdate
这条线时,意味着不改变状态而强制更新,需要调用forceUpdate()
这个方法,他可以不受阀门控制,直接触发下面的回调钩子componentWillReceiveProps
这个钩子,然后依次执行子组件中的下方的钩子函数新的生命周期
static getDerivedStateFromProps(props,state)
会在调用render方法之前进行调用,并且在初始化挂载以及更新之后都会被调用,它应该返回一个对象来更新state,如果返回null则不返回任何内容,如果返回state,则之后state的值将无法继续再更新了。
getSnapshotBeforeUpdate(prevProps,prevState)
在页面更新之前进行调用,它会返回一些信息,然后传递给componentDidUpdate(prevProps, prevState, snapshot)
父传子
props
来接收父组件传过来的数据子传父
如果想要在标签内部执行的方法中传入参数时,需要在执行函数的内部返回一个新的函数,或者在标签内部写成回调函数的形式,不然就会在浏览器一加载就会执行标签内部的方法!!!
消息订阅与发布
首先下载工具库、
npm i pubsub-js --save
使用
import PubSub from 'pubsub-js'
PubSub.publish('delete',data)// 发布消息
PubSub.subscribe('delete',function(data){}) //订阅消息
前端可以在package.json
中配置
"proxy":"http://localhost:5000"
对应的地址可以换成具体要请求的服务器
然后在发送ajax时,需要将路径换成当前脚手架启动的那个服务器路径;在请求资源时,会先在自身服务器下查找,如果有,则返回自身服务器下的资源,如果没有,则会去代理服务器那边去请求。
首先:创建代理配置文件
在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': ''}
})
)
}
在真正发送请求时,仍然需要向本地的服务器请求数据,如果要请求代理,需要在发送ajax的路径上端口号后面加上代理 /api1只有加上后,才会去请求代理服务器中的数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Y4c8Cv8-1632465085237)(C:\Users\。\AppData\Roaming\Typora\typora-user-images\image-20210920103956352.png)]
使用React的路由时,首先需要下载react-router-dom
npm i react-router-dom
然后需要在页面中引入,在使用react路由时,需要在外层包裹一个路由对象BrowserRouter或者是HashRouter
,与Vue的router-view
相类似;通常都会把这个外层路由对象直接包裹在index中App
组件的外侧
而路由的Link
标签与Vue的router-link
类似,象征着跳转路由的导航,在该标签中可以配置to
这个属性指定跳转地址
路由的Route
标签是直接映射路由的内容,可以在该标签内部配置path 和 component
属性,path用来匹配对应的路由地址,component用来匹配对应的路由组件。
路由组件与一般组件
1.写法不同:
一般组件:
路由组件:
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"
NavLink
相对于Link
标签来说,会使导航具有高亮效果,当然,也可以指定activeClassName
样式名
标签体内容是一个特殊的标签属性,通过this.props.children
可以抽取标签体内容。
可以使用Switch
标签把路由全部包起来,这样可以提高效率!
解决多级路径刷新页面样式丢失的问题
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter
路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Redirect的使用
1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
嵌套路由
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数
1.params参数
路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:this.props.match.params
2.search参数(ajax的query参数)
路由链接(携带参数):<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
备注:刷新也可以保留住参数
以上测试都是在BrowserRouter进行,不在hashRouter中进行!!!
params和search参数都会在地址栏显示,state参数不会在地址栏中显示
编程式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
如果一般组件也想使用路由的一些跳转方法,则需要通过从 react-router-dom 中引入
withRouter,withRouter是一个函数,需要将导出的组件传入withRouter的函数中。例如:withRouter(Demo) Demo是类组件的名字
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可以用于解决一些路径错误相关的问题。
图解:
components--->action对象-->store-->reducers-->store--->components
首先,Reducers会先初始化状态
然后,组件通过dispatch将action对象交给store,store把数据原来的状态previousState和action对象交给reducers,reducers将旧的数据加工完返回给store一个新的状态数据,然后store通过getState将最新的状态交给组件
精简版
redux只负责管理状态,不负责更新页面
我们会通过store.getState()去读取redux中的状态
我们会通过store.dispatch()去触发reducer中的函数取加工数据
我们一般会在挂载的生命周期钩子中去负责更新页面,例如:
// 在生命周期钩子中检测redux的状态的改变
componentDidMount(){
// 一旦数据改变就会重新调用一次render
store.subscribe(()=>{
// 使用setState来对render进行调用
this.setState({})
})
}
但是上面的这种方式比较繁琐,需要在每个组件都需要去重新渲染,比较麻烦,以下这种方式可以一劳永逸
我们在index.js中引入store
然后在这调用这个subscribe方法,就可以达到重新渲染页面的目的(注意:不是将原来的的ReactDOM.render函数剪切到这,而是复制一份进来!!!)
store.subscribe(() => {
ReactDOM.render( , document.getElementById('root'))
})
action为动作对象,type为动作的类型,data为动作对象操作的数据,type值为字符串,唯一的
{type:'ADD_STUDENT',data:{name:'tom',age:18}}
{}
一般对象形式的action为同步actionfunction
函数形式的action为异步action,异步action中一般会调用同步actiondispatch是一个函数,寓意着分发的意思,通过dispatch,把动作对象交给了store
store
store默认只接收一般对象的同步action,如果action的值是一个函数为一个异步action,那么就需要用到redux-thunk
这个中间件来接收异步action了
getState()
得到statedispatch(action)
分发action
,触发reducer调用,产生新的state
subscribe(()=>{})
订阅redux中状态的更改,当产生新的state时,自动调用reducers是负责加工状态,同时,reducers也可以初始化状态,第一次reducers也初始化状态了,然后从store传过来的previousState为undefined。Reducer的本质是一个函数,在reducer中只管最基本的动作,是加还是减;rudux中的reducer必须是一个纯函数
actions里边也都是暴露的函数
目录结构
如何使用接收异步action的中间件
npm i redux-thunk
在store.js下从redux-thunk引入thunk
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
从redux中引入可执行中间件这个函数
import {createStore,applyMiddleware} from 'redux'
最后在创建store时,applyMiddleware作为第二个参数传入,然后将thunk传入
export default createStore(countReducer,applyMiddleware(thunk))
容器组件与UI组件
UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
容器组件:负责和redux通信,将结果交给UI组件。
如何创建一个容器组件靠react-redux 的 connect函数
connect(mapStateToProps,mapDispatchToProps)
(UI组件)mapStateToProps
:映射状态,返回值是一个对象mapDispatchToProps
:映射操作状态的方法,返回值是一个对象1.容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
2.mapDispatchToProps,也可以是一个对象,为对象时,直接写操作数据的action即可
精简写法
容器组件和UI组件整合一个文件
无需自己给容器组件传递store,给包裹一个即可。
使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
mapDispatchToProps也可以简单的写成一个对象
一个组件要和redux“打交道”要经过哪几步?
定义好UI组件—不暴露
引入connect生成一个容器组件,并暴露,写法如下:
connect(
state => ({key:value}), //映射状态
{key:xxxxxAction} //映射操作状态的方法
)(UI组件)
在UI组件中通过this.props.xxxxxxx读取和操作状态
在redux中,使用push\pop
等方法时,为什么原来的数组不会发生改变,而使用扩展运算符就会生效?
实际上原来的数组也发生了变化,只不过页面没有发生跟新操作,因为redux在底层做了一个判断,如果返回的新数组和之前的数据所在的内存地址是一样的,redux就不会进行页面的更新,
开发者工具的使用
yarn add redux-devtools-extension
或者npm i redux-devtools-extension
.store中进行配置
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
从0开始如何搭建react-redux,从而共享数据
首先应该在index.js
入口文件下引入Provider import { Provider } from 'react-redux'
然后使用该组件将APP组件进行包裹,目的是让APP中的组件都可以共享store
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
在src目录下创建redux文件夹
创建store核心文件store.js
,该文件主要用于创建store核心仓库
// createStore() 用于创建redux中最为核心的store对象
// 引入applyMiddleware中间件,用于进行异步action的操作
import {createStore,applyMiddleware} from 'redux'
// 引入总的reducers
import allReducers from './reducers'
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
// 创建store并导出
export default createStore(allReducers,applyMiddleware(thunk))
创建constant.js
常量模块,这里用与创建多个action操作对象中的type常量
创建reduces文件夹,里边每一个js文件模块为对应的reducer所服务的模块
/*
该文件是为了创建一个为count组件服务的reducer,reducer的本质是一个函数
reducer会接受到两个参数
preState 之前的状态
action 动作类型对象
*/
import { INCREMENT, DECREMENT } from "../constant"
// 首先初始化状态
const initState = 0
export default function countReducer(preState = initState, action) {
const { type, data } = action
// 根据type判断如何加工数据
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
// 这块是个初始化的状态
return preState
}
}
创建actions的文件夹,这里边的每个js文件包含了每个模块对应的action动作对象
/*
该文件专门为count组件生成action对象
*/
import {INCREMENT,DECREMENT} from '../constant'
// 同步action
export function createIncrementAction(data) {
return { type: INCREMENT, data }
}
export function createDecrementAction(data) {
return { type: DECREMENT, data }
}
// 异步action 就是指action的值为函数
export function createIncrementAsyncAction(data,time) {
return (dispatch)=>{
setTimeout(()=>{
// 通知redux+data
dispatch(createIncrementAction(data))
},time)
}
}
在每一个容器组件如果想要共享redux中的数据,需要在组件引入connect
这个函数,而且需要将其暴露并传入一个UI组件,connect函数会接收到两个参数,第一个函数的返回值为redux总的状态对象,第二个函数的返回值为触发操作对象的方法,组件如果要调用对应的方法,则需要通过this.props.xx
的方式去触发
import { connect } from 'react-redux'
export default connect(
state=>({person:state.personArr}),
{
addPerson:createAddPerson
}
)(index)
(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函数中读取
3.对象式的setState需要获取原来的状态值,函数式的setState不需要获取,可以直接修改
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
// fallback回调的loading组件直接引入,不可以使用懒加载
// 需要懒加载的路由必须用 组件进行包裹
//2.通过指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行,不会监测任何状态,如果不传入第 二个参数,默认监测所有状态,传入谁,监测谁
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = React.useRef()
// 取值:refContainer.current.value
(3). 作用:保存标签对象,功能与React.createRef()一样
去除掉根标签div
// 这个标签只能够拥有一个key属性,其他属性不能传入
// 也可以将组件的根标签写成空标签,但是空标签不可以写任何属性
<>>
可以不用必须有一个真实的DOM根标签了
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
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就是context中的value数据
value => (
要显示的内容
)
}
</xxxContext.Consumer>
在应用开发中一般不用context, 一般都用它的封装react插件
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
只有当组件的state或props数据发生改变时才重新render()
Component中的shouldComponentUpdate()总是返回true
办法1:
重写shouldComponentUpdate()方法 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
从react中导入PureComponent,让组件去继承,而不是直接继承Component
使用PureComponent PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
使用PureComponent只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false;
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
Vue中:
使用slot技术, 也就是通过组件标签体传入结构
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
其实render属性可以写成任意名字,只不过在调用的时候需要写的方法名一致才行!
xxxx
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
}>
A组件: {this.props.render(内部state数据)} 这个等同于Vue的插槽技术,相当于挖一个坑,让其他数据来填补
C组件: 读取A组件传入的数据显示 {this.props.data}
可以在父组件中使用render这个属性来渲染子组件,render内部可以传入一个回调函数,在回调函数中可以把要传递的数据带过去,然后可以供子组件去使用
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
getDerivedStateFromError配合componentDidCatch
state = {
hasError:'', // 用于标识子组件是否出现c
}
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error); // 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
Date.now()
或者Math.random()
等常见的高阶函数
forEach\map\filter\reduce\find\bind