之前,学习过React,但是2、3个月没用的话,就是忘得差不多了,在最近的这段时间里发现大厂用React技术栈很多,然后就特意去温习了一下,整个感觉就是React挺吃JS的,国庆期间然后卷了一波,成为他们口中所说的“卷王!”
React全家桶
一、初识React
首先需要引入React库,react.development.js
需要在react-dom.development.js
之前引入,因为react.development.js
是核心库。
二、jsx的语法编写规则
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式要用
{}
包起来 - 标签内样式的类名指定不能使用
class
,得用className
- 在标签内写内联样式,要用
style={{color:'red',fontSize:'12px'}}
形式去写 - 虚拟DOM只有一个根标签
- 所有的标签必须闭合
- 标签的首字母
- 若小写字母开头,则将该标签转为html中同名的元素。没有同名元素会报错
- 若大写字母开头,React就会去渲染对应的组件。如果组件没有定义,则就会去报错
如果想要写判断条件,可以写成三元表达式,三元表达式可以嵌套使用
2.1 区分js语句代码与js表达式
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,下面这些都是表达式
a
a+b
main()
arr.map()
function test(){}
语句代码:下面这些都是语句代码
if(){}
for(){}
switch(){}
三、React中的组件
3.1 函数式组件
3.2 类式组件
3.3 组件实例的三大核心属性
3.3.1 state
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 通过更新组件的state来更新对应的页面显示(重新渲染组件)
注:
1. 组件中render方法中的this为组件实例对象
2. 组件自定义方法中的this为undefined,如何解决?
- 强制绑定this:通过函数对象的bind()
- 使用箭头函数
3. 状态数据,不能直接修改,得通过setState来对数据进行更新
state的基本使用
state的简写方式如下:
类组件中在创建之后发生了什么?
- 首先需要在类组件内部直接初始化state的状态
- 接着在
render()
函数内部定义常量,拿到state中的数据后,return
出需要展示的内容 - 在该类组件内部可以自定义许多方法,但是为了使方法挂载到该类的实例上,需要用
赋值语句+箭头函数的形式
来自定义方法,从而避免this指向丢失的问题
最后,采用ReactDOM.render()
的方式,将组件渲染到页面上。
3.3.2 props
- props是每个组件对象都会有的属性
- 组件标签的所有属性都保存在props中
作用:
- 通过标签属性从组件外向组件内传递变化的数据
- 组件内部不要修改props数据
props的基本使用
对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(<Person name="貂蝉" age="90" sex="女"/>,document.getElementById('test3'))
// 批量传递props
const p = {name:'宋庆扬',age:20,sex:'女'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak() {
console.log('我说话了')
}
</script>
</body>
</html>
props的简写方式
在该类组件的内部,在需要添加的属性前面加上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(<Person name="貂蝉" age="90" sex="女"/>,document.getElementById('test3'))
// 批量传递props
const p = { name: '宋庆扬', age: 20, sex: '女' }
ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
function speak() {
console.log('我说话了')
}
</script>
</body>
</html>
3.3.3 refs与事件处理
理解:组件内的标签可以定义ref属性来标识自己
字符串形式的ref
回调的ref
ref直接接收一个回调,该回调的参数就是该元素的节点,随后起一个常量名用来接收该节点。
ref回调次数的问题
ref方式数的方式定义的,在更新的过程中它会被执行两次,第一次传入的参数为null,第二次传入的参数才是dom元素。这是因为在每次渲染时会创建一个新的函数实例,React会清空旧的ref然后再设置一个新的。
解决方案
将ref的回调函数定义成一个class的绑定函数的方式可以避免以上问题,但是写成内联函数的方式也无关紧要
创建Ref容器
this.ref2.current
指代的就是当前ref2所指向的根节点
ref中的事件处理
- 通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件,而不是使用的原生DOM事件—–为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)—–为了更加高效
- 通过event.target得到发生事件的DOM元素对象—–不要过度使用ref
- 即使在某一个根元素中不传入ref,也可以发生回调事件,前提是只能发生在某个根节点上的自身的事件
四、React如何将输入的值同步更新到状态中
首先输入类的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>
五、React生命周期
旧的生命周期
卸载组件的方法为:ReactDOM.unmountComponentAtNode(document.getElementById('box'))}
组件挂载时,按照图中的顺序依次执行
当组件更新时
- 当组件走setState那条线时,
shouldComponentUpdate默认返回true
,它是组件更新的总开关,如果它返回的是false,组件将无法更新,那么之后的回调也将无法继续执行,程序只能执行到阀门这块 - 当组件走
forceUpdate
这条线时,意味着不改变状态而强制更新,需要调用forceUpdate()
这个方法,他可以不受阀门控制,直接触发下面的回调钩子 - 当组件走最上方那条线时,父组件向子组件标签内传递了数据,那么将会触发
componentWillReceiveProps
这个钩子,然后依次执行子组件中的下方的钩子函数
新的生命周期
static getDerivedStateFromProps(props,state)
会在调用render方法之前进行调用,并且在初始化挂载以及更新之后都会被调用,它应该返回一个对象来更新state,如果返回null则不返回任何内容,如果返回state,则之后state的值将无法继续再更新了。
getSnapshotBeforeUpdate(prevProps,prevState)
在页面更新之前进行调用,它会返回一些信息,然后传递给componentDidUpdate(prevProps, prevState, snapshot)
六、父子组件通信
父传子
- 父组件直接将自身的数据传递到子组件中的标签内部,然后子组件通过
props
来接收父组件传过来的数据
子传父
- 子传父时,需要父组件通过props传递给子组件一个回调函数,子若想传数据,需要调用这个函数,将数据传入到参数中
如果想要在标签内部执行的方法中传入参数时,需要在执行函数的内部返回一个新的函数,或者在标签内部写成回调函数的形式,不然就会在浏览器一加载就会执行标签内部的方法!!!
消息订阅与发布
-
首先下载工具库、
npm i pubsub-js --save
-
使用
import PubSub from 'pubsub-js' PubSub.publish('delete',data)// 发布消息 PubSub.subscribe('delete',function(data){}) //订阅消息
七、React前端配置代理
-
前端可以在
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的路由时,首先需要下载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.写法不同:
一般组件:<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"
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可以用于解决一些路径错误相关的问题。
九、redux
图解:
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(<App />, document.getElementById('root'))
})
-
action为动作对象,type为动作的类型,data为动作对象操作的数据,type值为字符串,唯一的
- 例如:
{type:'ADD_STUDENT',data:{name:'tom',age:18}}
- action可以分成两种
{}
一般对象形式的action为同步action- 如果action的值为一个函数,
function
函数形式的action为异步action,异步action中一般会调用同步action
- 例如:
-
dispatch是一个函数,寓意着分发的意思,通过dispatch,把动作对象交给了store
-
store
store默认只接收一般对象的同步action,如果action的值是一个函数为一个异步action,那么就需要用到
redux-thunk
这个中间件来接收异步action了- 通过
getState()
得到state - 通过
dispatch(action)
分发action
,触发reducer调用,产生新的state
- 通过
subscribe(()=>{})
订阅redux中状态的更改,当产生新的state时,自动调用
- 通过
-
reducers是负责加工状态,同时,reducers也可以初始化状态,第一次reducers也初始化状态了,然后从store传过来的previousState为undefined。Reducer的本质是一个函数,在reducer中只管最基本的动作,是加还是减;rudux中的reducer必须是一个纯函数
- 初始化时,data的值可以不传;
- 加工时,根据旧的state和action生成新的state和纯函数
-
actions里边也都是暴露的函数
Redux完整版
目录结构
- constant,里边包含了action对象中的type的常量模块 定义常量
- actions,里边包含了加工数据的动作对象 生成操作对象
- reducer,里边包含了加工数据的具体方法 具体对数据进行CRUD
- store,里边是redux的核心对象 创建store对象
如何使用接收异步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))
十、react-redux
容器组件与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
入口文件下引入Providerimport { 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)
-
十一、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函数中读取
3.对象式的setState需要获取原来的状态值,函数式的setState不需要获取,可以直接修改
2. lazyLoad
路由组件的lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
// fallback回调的loading组件直接引入,不可以使用懒加载
// 需要懒加载的路由必须用<Suspence></Suspence>组件进行包裹
//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 = React.useRef()
// 取值:refContainer.current.value
(3). 作用:保存标签对象,功能与React.createRef()一样
4. Fragment
使用
去除掉根标签div
// 这个标签只能够拥有一个key属性,其他属性不能传入
<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就是context中的value数据
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:
从react中导入PureComponent,让组件去继承,而不是直接继承Component
使用PureComponent PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
使用PureComponent只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false;
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
7. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
其实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数据)} 这个等同于Vue的插槽技术,相当于挖一个坑,让其他数据来填补
C组件: 读取A组件传入的数据显示 {this.props.data}
可以在父组件中使用render这个属性来渲染子组件,render内部可以传入一个回调函数,在回调函数中可以把要传递的数据带过去,然后可以供子组件去使用
8. 错误边界
理解:
错误边界(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);
}
9. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
十二、纯函数和高阶函数
1、什么是纯函数?
- 只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下条件
- 不改写参数数据
- 不发送网络请求,以及输入和输出
- 不调用
Date.now()
或者Math.random()
等
2.什么是高阶函数?
- 参数是一个函数
- 返回值是一个函数
常见的高阶函数
- 定时器设置函数
- 数组的
forEach\map\filter\reduce\find\bind
- promise
- react-redux中的connect函数