这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
组件通信
组件是独立且封闭的单元,默认情况下组件只能使用自己的数据(state)
组件化开发的过程中,完整的功能会拆分多个组件,在这个过程中不可避免的需要互相传递一些数据为了能让各组件之间可以进行互相沟通,数据传递,这个过程就是细件通信
组件通信的方式包括:
1.父子关系-最重要的
2.兄弟关系-自定义事件模式产生技术方法 eventBus/通过共同的父组件通信
3.其它关系-mobx/redux/基于hook的方案
父传子
实现父子通信中的父传子,把父组件中的数据传给子组件
实现步骤:
1,父组件提供要传递的数据-state
2,给子组件标签添加属性值为state中的数据
3,子组件中通过props接收父组件中传过来的数据
3.1,类组件使用this.props获取props对象
3.2.函数式组件直接通过参数获取props对象
//子组件
function SonF(props) {
return <div>我是函数子组件,{props.msg}</div>
}
class SonC extends React.Component {
render() {
// 类组件必须通过this关键词去获取,这里的props是固定的
return <div>我是类子组件,{this.props.msg}</div>
}
}
//父组件
class Father extends React.Component {
// 准备数据
state = {
message: 'this is father-message',
}
render() {
return (
<div className="five">
{/* 子组件身上绑定属性,属性名可以自定义 保持语义化 */}
<SonF msg={this.state.message} />
<SonC msg={this.state.message}></SonC>
</div>
)
}
}
注意子组件使用父组件的数据是依靠自定义的属性名而不是父组件里state里面的某个数据名
关于props
1.props是只读对象(readonly)
根据单项数据流的要求,子组件只能读取props中的数据,不能进行修改
this.props.msg ='new msg'//这样是不可以的,不可直接进行修改
2.props可以传递任意数据
数字、字符串、布尔值、数组、对象、、函数、JSX(用props传递jsx等于vue中的模板插槽)
各种类型数据传递示例
function Son(props){
//props是一个对象 里面存着通过父组件传入的所有数据
return(
<div>我是函数子组件,{props.list.map(item =><p key={item}>{item}</p>)}</div>
)
state = {
userinfo:{
name:'cp',
age:}
}
----------------------------------------
function Son(props){
//props是一个对象 里面存着通过父组件传入的所有数据
return(
<div>我是函数子组件,{props.userinfo.name}</div>
)
<button onClick={props.getMes)>触发父组件传入的函数</button>
<child={<slan>this is span</span>} /Son>//父组件传子组件
-----------------------------------------
{props.child}//子组件调用
子组件调用父组件数据时可以使用解构赋值
function Son (props)
{
// props是一个对象 里面存着通过父组件传入的所有数据
console.log (props)
// 解构赋值可以吗?会有什么影响吗?可以解构
const { list, userInfo, getMes, child } = props
return (
<div>我是函数子组件,
{list.map(item => <p key={item}>{item}</p>}}
{userInfo.name}
<button onClick={getMes}>触发父组件传入的函数</button>
{child}
</div>
)
}
函数组件也可以在函数参数的位置进行解构
function Son ({ list, userInfo, getMes, child })
{
return (
<div>我是函数子组件,
{list.map(item => <p key={item}>{item}</p>}}
{userInfo.name}
<button onClick={getMes}>触发父组件传入的函数</button>
{child}
</div>
)
}
子传父
子组件调用父组件传递过来的函数,并且把想要传递的数据当成函数的实参传入即可
具体步骤:
1、准备一个函数传给子组件
class Father extends React.Component {
// 准备数据
state = {
message: 'this is father-message',
}
getsonMsg = (sonMsg) => {
console.log(sonMsg)
}
render() {
return (
<div className="five">
{/* 子组件身上绑定属性,属性名可以自定义 保持语义化 */}
<SonF getsonMsg={this.getsonMsg} />
</div>
)
}
}
//子组件
function SonF(props) {
const { getsonMsg } = props
return (
<div>
我是函数子组件
<button onClick={() => getsonMsg('这是来自子组件的信息')}>clickme</button>
</div>
)
}
当然事件处理函数也是可以另外定义成一个函数的,如:
兄弟组件之间通信
核心思路:通过状态提升机制,利用共同的父组件实现兄弟通信
如A想发数据给B,那么A先发给父组件,然后再由父组件发给B
//子组件
function SonA(props) {
const { getsonMsg } = props
const Amsg = '这是来自A组件的数据'
return (
<div>
this is A
<button
onClick={() => {
getsonMsg(Amsg)
}}
>
发送数据
</button>
</div>
)
}
//子组件
function SonB(props) {
const { sendBmsg } = props
return (
<div>
this is B
<br />
<span>{sendBmsg}</span>
</div>
)
}
//父组件
class Father extends React.Component {
// 准备数据
state = {
message: 'this is father-message',
sendBmsg: '父传子初始测试数据',
}
getsonMsg = (sonMsg) => {
console.log(sonMsg)
this.setState({
sendBmsg: sonMsg,
})
}
render() {
return (
<div className="five">
{/* 子组件身上绑定属性,属性名可以自定义 保持语义化 */}
<SonA getsonMsg={this.getsonMsg} />
<SonB sendBmsg={this.state.sendBmsg} />
</div>
)
}
}
使用context实现跨组件传递数据
上图是一个react形成的嵌套组件树,如果我们想从App组件向任意一个下层组件传递数据,该怎么办呢?目前我们能采取的方式就是一层一层的props往下传,显然很繁琐那么,Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
实现 步骤:
1,创建Context对象导出Provider和Consumer对象
const { Provider, Consumer } = createContext()
2,在根组件内部使用Provider包裹根组件提供数据
<Provider value={this.state.message}>
{/* 根组件 */}
</Provider>
//Father->A->B
const { Provider, Consumer } = createContext()
//子组件
function SonA(props) {
return (
<div>
this is A
<SonB />
</div>
)
}
//孙组件
function SonB(props) {
return (
<div>
this is B<Consumer>{(value) => <span>{value}</span>}</Consumer>
</div>
)
}
//父组件
class Father extends React.Component {
// 准备数据
state = {
message: 'this is father-message',
}
render() {
return (
//注意provider包裹的内容
<Provider value={this.state.message}>
<div className="five">
{/* 子组件身上绑定属性,属性名可以自定义 保持语义化 */}
<SonA />
</div>
</Provider>
)
}
}
3,在孙组件中需要用到数据的组件使用Consumer包裹获取数据
<Consumer
{value => /* 基于 context 值进行渲染*/}
</Consumer>
children属性
children属性是什么
表示该组件的子节点,只要组件内部有子节点,props中就有该属性
children可以是什么
1.普通文本
2.普通标签元素
3.函数
4.JSX
如
props校验---场景和使用
对于组件来说,props是由外部传入的,我们其实无法保证组件使用者传入了什么格式的数据,如果传入的数据格式不对,就有可能会导致组件内部错误,有一个点很关键---组件的使用者可能报错了也不知道为什么,看下面的例子
props本应传入的是一个数组,因为只有数组才有map方法,但是实际上传入的却是number,所以我们需要加入props校验
实现步骤:
1.安装属性校验包:yarn add prop-types
2.导入 prop-types 包
3.使用组件名.propTypes ={}给组件添加校验规则
组件生命周期
组件的生命周期是指组件从被创建到挂载到页面中运行起来,再到组件不用时卸载的过程,注意,只有类组件才有生命周期(类组件 实例化 函数组件 不需要实例化)
挂载阶段
执行顺序:
注意不能在render中调用setState!!因为setState会使页面重新渲染导致再次触发render
更新阶段
注意不能在componentDidUpdate中调用setState!!因为setState会使页面重新渲染导致再次触发componentDidUpdate
卸载阶段
一个组件被销毁时,在这个组件中使用的所有定时器都应该要清理
PS:
如果数据是组件的状态需要去影响视图 定义到stalte中
而如果我们需要的数据状态 不和视图绑定 定义成一个普通的实例属性就可以啦,比如定时器就可以不用定义在state中
state中尽量保持精简
Hooks
什么是hooks?
Hooks的本质:一套能够使函数组件更强大,更灵活的“钩子”
React体系里组件分为类组件和函数组件
经过多年的实战,函数组件是一个更加匹配React的设计理念 UI = f(data),也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从react v16.8开始,Hooks应运而生
注意点:
1,有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
2,有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
3.hooks只能在函数组件中使用
Hooks解决了什么问题
Hooks的出现解决了俩个问题1.组件的状态逻辑复用2.class组件自身的问题
1.组件的逻辑复用
在hooks出现之前,react先后尝试了mixins混入,HOC高阶组件,render-props等模式但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等
2.class组件自身的问题
class组件就像一个厚重的‘战舰,一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的"快艇"
Hoos的优势
1.告别难以理解的 Class
2.解决业务逻辑难以拆分的问题
3.使状态逻辑复用变得简单可行
4.函数组件在设计思想上,更加契合React的理念
useState
用法:
1.导入usestate函数 react
2.执行这个函数并且传入初始值 必须在函数组件中
3.[数据,修改数据的方法]
4.使用数据 修改数据
import { useState } from 'react'//注意这里导包需要用{}包裹
function APP() {
const [Count, setCount] = useState(0)//注意这里用的是数组来解构而不是{}
return (
<div className="APP">
<button
onClick={() => {
setCount(Count + 1)
}}
>
{Count}
</button>
</div>
)
}
export default APP
状态的读取和修改
const [Count, setCount] = useState(0)//注意这里用的是数组来解构而不是{}
1、这里useState传过来的参数 作为count的初始值
2、解构赋值的数组中变量的名字可以自定义,保持语义化,但是这两个变量的顺序不能变
3、setCount函数 作用用来修改count 依旧保持不能直接修改原值还是生成一个新值替换原值的原则
格式为:setCount(基于原值计算得到的新值),要注意++ 、--这些操作都不能写
4、count和setCount是一对 是绑在一起的 setCount只能用来修改对应的count值
组件的更新过程
首次渲染
首次被渲染的时候 组件内部的代码会被执行一次
其中useStatd也会跟着执行 这里重点注意 初始值只在首次渲染时生效
更新渲染 setCount都会更新
1.app组件会再次渲染 这个函数会再次执行
2.useState再次执行 得到的新的count值不是0而是修改之后的1 模板会用新值渲染
重点一句话:useState初始值只在首次渲染生效 后续只要调用setCount整个app中代码都会执行
此时的count每次拿到的都是最新值
useState的使用规则
1.useState函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
useEffect
副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于React组件来说,主作用是根据数据(state/props)渲染UI,除此之外都是副作用(比如,手动修改DOM)
依赖项控制副作用
1、默认状态(无依赖项)
组件初始化的时候先执行一次 等到每次数据修改组件更新再次执行
2、添加空数组
在useEffect函数中添加空数组依赖项,组件只在首次渲染时执行一次
useEffect(()=>{
console.log('副作用执行了')
},[])
3、添加特定依赖项
副作用函数在首次渲染时执行一次,并且在特定的依赖项发生变化时重新执行
注意:
只要在useEffect回调函数中用到的数据状态就应该出现在依赖项数组中声明否则可能会有bug
某种意义上hook的出现 就是想不用生命周期概念也可以写业务代码
自定义hook修改localstorage里的值
const[message,setMessage]= useLocalStorage(defaultValue)
1.message可以通过自定义传入默认初始值
2.每次修改message数据的时候都会自动往本地同步一份
import { useState, useEffect } from 'react'
export function useLocalStorage(key, defaultvalue) {
const [message, setMessage] = useState(defaultvalue)
useEffect(() => {
window.localStorage.setItem(key, message)
}, [message, key])
return [message, setMessage]
}
function App() {
const [message, setMessage] = useLocalStorage('hook-key', '111')
setTimeout(() => {
setMessage('222')
}, 5000)
return (
<div>
{message}
</div>
)
}
useState回调函数的参数
参数只会在组件的初始渲染中起作用,后续渲染会被忽略,如果初始state需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被调用
语法:
const[name,setName]= useState(()=>{
//编写计算逻辑
return'计算之后的初始值'})
语法规则:
1.回调函数return出去的值将作为name的初始值
2.回调函数中的逻辑只会在组件初始化的时候执行一次
语法选择:
1、如果就是初始化一个普通的数据 直接使用useState(普通数据)即可
2、如果要初始化的数据无法直接得到需要通过计算才能获取到,使用useState(()=>())
function Counter(props) {
const [count, setCount] = useState(() => {
//这里目的为了体现初始值经过一定的计算
//这个计算比较广义的概念
//只要无法直接确定 需要通过一定的操作才能获取 就可以理解为计算
return props.count
})
return (
<button
onClick={() => {
setCount(count + 1)
}}
>
{count}
</button>
)
}
function APP()){
return(
<div>
<Counter count={10} />
<Counter count={20} />
</div>
)
}
useEffect清除副作用
在组件被销毁时,如果有些副作用操作需要被清理,就可以使用此语法,比如常见的定时器
简单来说就是在useEffect方法的return中清除副作用
import React, { useState, useEffect } from 'react'
function Test() {
useEffect(() => {
let timer = setInterval(() => {
console.log('定时器执行了')
}, 1000)
return () => {
clearInterval(timer)
}
})
return <div>this is test</div>
}
function App() {
const [flag, setflag] = useState(true)
return (
<div>
{flag ? <Test /> : null}
<button onClick={() => setflag(!flag)}>switch</button>
</div>
)
}