最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React类组件基础02-组件间通信和生命周期

    正文概述 掘金(普通上班族)   2020-12-10   452

    组件间通信

    如果不借助第三方库,原生react是只支持单向流动:父级可以向子级通过props的方式发送state和回调函数,而子级不能向父级发送事件或者修改父级的props。所以如果子级想要修改父级组件的状态(也就是数据),只可以通过父级传递下来的回调函数进行执行。

    相邻层级组件间的通信(同级/父子)

    借助上一篇笔记中最后写到的实例,如果我们想要在展开一栏的时候,关闭掉其他的栏,可是在App组件中是直接实例化了三个Dl组件,这三个组件之间是无法互相通信的,只有在他们的父组件(也就是App)中声明一个state状态,利用props传递给每个实例,并声明一个改变该state状态的回调函数,一并传给三个实例。这样当每个Dl实例触发这个回调函数时,App组件就会修改state状态,从而实现了同级组件之间的通信

    //App组件
    //声明一个state状态,负责表示哪个Dl窗口是展开的。并声明一个回调函数,负责控制state状态。
    state = {
        openname: "" //openname 记录的是当前应该哪一项展开,如果为空则都不展开
    }
    changeOpen = (name) => { //回调函数触发时,会讲修改state状态
        this.setState({
            openname: name
        })
    }
    render() {
        const {openname} = this.state;
        return <div className="friend-list">
            {Object.keys(data).map((item,index)=>{
                return <Dl 
                  key={index} 
                  data={data[item]} 
                  name={item}
                  openname={openname} //把这个状态传递给所以实例化的Dl组件
                  changeOpen = {this.changeOpen} //把更改openname状态的回调函数也传递给所有Dl组件
                />
            })}
        </div>
      }
    
    // Dl组件
    render() {
        let { data, name, openname, changeOpen } = this.props; // 从props中拿到openname和changeOpen
        return <dl className={`friend-group ${openname === name? "expanded" : ""}`}>  <!--通过openname判断是否展开-->
          <dt onClick={() => {
           changeOpen(openname === name?"":name); //通过拿到的changeOpen回调函数,修改openname状态
          }}>{data.title}</dt>
          {data.list.map((item, index) => {
            return <dd key={index}>{item.name}</dd>
          })}
        </dl>
      }
    

    跨级组件间通信(爷孙)

    假设有这样三个组件:

    • App组件:是根组件
    • Child组件:是App组件的子组件
    • SubChild组件:是Child组件的子组件

    这样如果SubChild组件接收来自App组件的信息,那么中间必须要过一道Child,如果用笨方法,可以通过一层层的拿到props=>结构props=>传递给下一层=>下一层继续结构props,这样慢慢的传递下去。显然这样可以,但是很麻烦,所以原生React提供了一种跨级组件通信的方式(有点类似vuex)—— context

    首先需要通过createContext()方法声明一个context实例,这里为了方便区分,就叫它contextStorecontextStore实例提供了两个方法ProviderConsumer,从名字就可以看出来是分别表示提供和接收状态的。

    import {createContext} from "react";
    const contextStore = createContext();
    const {Provider,Consumer} = context;
    export default contextStore;
    export {Provider,Consumer};
    

    在提供状态的App组件中,将需要借用context方法跨级传递的状态放在<Provider>标签的props里面,标签内包裹上需要转递给的组件。

    render() {
        const {count,name} = this.state;
        return <>
          <Provider
            value={{
              count,
              name,
              setCount:this.setCount
            }}
          >
            <Child />
          </Provider>  
        </>
      }
    

    这样在Child组件里面就不需要做任何处理,SubChild组件就可以直接拿到数据。当然,如果Child组件想要用这些数据,还是可以直接用this.props取到。取数据有两种方式,分别是使用<Consumer>标签获取和通过contextType静态属性声明。

    通过<Consumer>,标签内包裹需要返回的虚拟DOM结构,需要的数据要从context中解构出来。

      render() {
        return <Consumer>
          {(context) => {
            const { count, name } = contextStore;
            return <div>
              <p>count:{count}</p>
              <p>name:{name}</p>
            </div>
          }}
        </Consumer>
      }
    

    还有一种方式,就是通过声明Component类自带的静态属性contextType来告诉类组件从哪里获取数据

    class SubChild extends Component {
      static contextType = contextStore;// contextType ,告诉当前组件,当前SubChild组件中context值,从上面写的context.hs文件导出的contextStore实例中获取,也就是可以理解为 let this.context = contextStore。
      render() {
        const { count, name, setCount } = this.context; //这个this.context就是类组件自己的context了,注意不要跟上面的contextStore搞混
        return <div>
          <p>count:{ count}</p>
          <p>name:{name }</p>
          <button onClick={()=>{
            setCount(count +1);
          }}>递增</button>
        </div>
      }
    }
    

    PS:不推荐直接使用context处理跨组件通信。context一般是给第三方库用的,后续会通过第三方库来处理跨级通信问题。

    类组件的生命周期

    挂载阶段

    组件创建 => 把组件创建的虚拟DOM => 生成真实DOM =>添加到我们的DOM树中

    • constructor:组件初始化

    • static getDerivedStateFromProps(props) :将传递进来的props关联到state里面,注意这个函数一定要有返回值

    • componentDidMount:生成真实的DOM节点,通常在这个阶段处理副作用(请求)

    • render:组件完成挂载,虚拟DOM已经生成了真实DOM

    class Child extends Component {
      constructor(props){
          super(props);
          console.log(0,"组件初始化");
      }
      // 将props 关联到state中
      static getDerivedStateFromProps(props){
          //console.log(this,props);该方法中没有 this
          console.log(1," 将props 关联到state中");
          return { //该生命周期函数必须有返回值,返回值中保存的是从 props 中 要关联到 state 中数据
            info: "要关联的数据"
          }
      } 
      state={
        count: 1
      }
      componentDidMount(){
        console.log(3,"组件挂载完成,虚拟DOM,已经生成了真实DOM");
      }
      render() {
        console.log(2,"生成虚拟DOM",this.state);
        const {count} = this.state;
        return <div>
            <p>{count}</p>
            <button onClick={()=>{
              this.setState({
                count: count + 1
              })
            }}>递增</button>
        </div> 
      }
    }
    

    更新阶段

    组件状态更新 => 组件重新渲染,从setstate之后组件就开始更新,一直到完成对真实的DOM节点更新

    static getDerivedStateFromProps(props, state):这是一个静态方法,用来从props冲获取最新的state

    shouldComponentUpdate():判断组件是否需要更新,以及哪些地方需要更新

    render():重新渲染

    getSnapshotBeforeUpdate():已经完成了新的虚拟DOM节点构建,但是没有更新到真实DOM,用来获取到更新前的DOM快照

    componentDidUpdate():组件更新完成

    class Child extends Component {
      // 将props 关联到state中
      static getDerivedStateFromProps(props){
          //console.log(this,props);该方法中没有 this
          console.log(1," 将props关联到state中");
          
          return { //该生命周期函数必须有返回值,返回值中保存的是从 props 中 要关联到 state 中数据
            info: "要关联的数据"
          }
      } 
      state={
        count: 1
      }
      shouldComponentUpdate(nextProps,nextState){
          //nextProps 更新后的props, nextState 更新后的state
          //console.log(nextState,this.state);
          console.log(2,"判断组件是否需要更新");
          return true; //返回值为 true 则继续执行更新,返回值为false 则不执行更新,后续的生命周期函数,也不会执行
      }
      getSnapshotBeforeUpdate(){
        //已经完成新的虚拟DOM的构建,但是还未更新真实DOM
        //用来获取更新前的DOM快照
        console.log(4,"已经完成新的虚拟DOM的构建,但是还未更新真实DOM");
        let box = document.querySelector("#box");
        return box.innerHTML;//改返回值会传递给componentDidUpdate的prevDOM参数
      }
      componentDidUpdate(prevProps,prevState,prevDOM){
        // console.log(prevDOM);
        console.log(5,"组件更新完成");
      }
      render() {
        console.log(3,"生成虚拟DOM",this.state);
        const {count} = this.state;
        return <div id="box">
            <p>{count}</p>
            <button onClick={()=>{
              this.setState({
                count: count + 1
              })
            }}>递增</button>
        </div> 
      }
    }
    

    卸载阶段

    componentWillUnmount():组件即将销毁,可以在这个阶段清空各种监听事件。

    class Child extends Component {
      state={
        count: 1
      }
      componentDidMount(){
        let getSize = ()=>{
          let size = document.querySelector("#size");
          size.innerHTML = window.innerWidth;
        }
        getSize();
        window.onresize = getSize;
      }
      componentWillUnmount(){
        window.onresize = null;
      }
      render() {
        const {count} = this.state;
        return <div id="box">
            <p>{count}</p>
            <button onClick={()=>{
              this.setState({
                count: count + 1
              })
            }}>递增</button>
            <div id="size">0</div>
        </div> 
      }
    }
    

    受控组件

    Vue给我提供了一个语法糖v-model来实现表单与数据的双向绑定,但是React比较懒,专注于渲染视图,并没有提供这样的语法糖,需要我们通过stateprops手动声明,声明好之后就形成了一个双向绑定的受控组件。会用到这个受控组件的场景也很类似,需要输入的表单或者需要打钩的CheckBox

    • value: 将组件的状态,赋值给控件的value属性,通过onChange事件监听控件的value属性发生变化,再通过回调函数赋值给组件的state

    • checked:将组件的状态,赋值给控件的 checked 属性,通过 onChange 事件监听控件的 checked 属性发生变化,再通过回调函数赋值给组件的state

    class App extends Component {
      state={
        val:""
      }
      componentDidMount(){
        let text1 = document.querySelector("#text1");
        text1.oninput = function(){
          console.log(this.value);
        }
      }
      render() {
        const {val} = this.state;
        return <div>
            <input
               type="text" 
               value={val}  
               onChange={({target})=>{
                  this.setState({
                    val: target.value
                  })
               }}
            />
            <input type="text" id="text1" />
            <input type="text" id="text2" defaultValue="初始值" />
            <p>{val}</p>
            <button onClick={()=>{
              this.setState({
                val:""
              })
            }}>重置</button>
        </div>
      }
    }
    

    非受控组件

    1. 需要设置初始值,只设置初始值,并不希望该控件受控,不要用value|checked,因为只要用 value|checked 的话react 就认为我们做受控组件。所以这里要用 defaultValuedefaultChecked

      <input
      type="text" 
      defaultValue={val}  
      onChange={({target})=>{
         this.setState({
           val: target.value
         })
      }}
      />
      
    2. 初始值,直接留空。

    实例

    // App.js
    
    import { Component } from "react";
    import "./index.css";
    import AddMeaasge from "./AddMeaasge";
    import MessageList from "./MessageList";
    
    class App extends Component{
        state = {
            data:[{
                    id:0,
                    name:"宋成昊",
                    msg:"作业暗号:海哥真帅"
                }]
        };
        addMeaasge = (newName, newMsg) => {
            const {data} = this.state;
            data.push({
                id:Date.now(),
                name:newName,
                msg:newMsg
            });
            this.setState({
                data
            })
    
        };
        remove = (id) => {
            const {data} = this.state;
            this.setState({
              data:data.filter(id => {return data.id = !id })
            })
        }
        render(){
            let { data } = this.state
            return <section className="wrap">
            <h2 className="title">留言板</h2>
            <AddMeaasge 
                addMeaasge = {this.addMeaasge}
            />
            <MessageList 
                data = { data }
                remove = { this.remove }
            />
        </section>
        };
    }
    
    export default App;
    
    // MessageList.js
    
    import { Component } from "react";
    
    class MessageList extends Component{
        render(){
            const { data,remove } = this.props
    
            return <ul className="messageList">
            {data.map((item,index) => {
                return <li key={index}>
                <h3>{item.name}</h3>
                <p>{item.msg}</p>
                <a onClick={()=>{
                    const id =item.id
                    remove(id)
                }}>删除</a>
            </li>
            })}
        </ul>
        };
    };
    
    export default MessageList
    
    //AddMeaasge.js
    
    import { Component } from "react";
    
    class AddMeaasge extends Component{
        state={
            newName:"",
            newMsg:""
        }
        render(){
            const {newName, newMsg} = this.state
            const { addMeaasge } = this.props
            return <div className="addMessage">
            <input 
                type="text" 
                placeholder="请输入昵称" 
                autoComplete="off"
                value={newName}
                onChange={({target})=>{
                    this.setState({
                        newName:target.value
                    })
                }}
                />
            <textarea 
                placeholder="请输入留言"
                value={newMsg}
                onChange={({target})=>{
                    this.setState({
                        newMsg:target.value
                    })
                }}
                ></textarea>
            <button
                onClick={() => {
                    addMeaasge(newName,newMsg);
                    this.setState({
                        newName:"",
                        newMsg:""
                    })
                }}
            >提交留言</button>
        </div>
        }
    };
    
    export default AddMeaasge
    
    // index.css
    // 略
    

    最终效果:

    React类组件基础02-组件间通信和生命周期


    起源地下载网 » React类组件基础02-组件间通信和生命周期

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元