基于react的todolist实现(2)

多层次组件通讯

组件通讯:

1、父子通讯:props;

  • 父组件操作:定义属性并传递数据
  • 子组件操作:函数组件:函数的第一个参数、类组件:this.props

2、子父通讯:把父组件的方法传到子组件去执行并传递参数;

  • 父组件操作:定义方法并通过props传入子组件 (数据在哪里就把方法定义在哪里)
  • 子组件操作:执行方法并传递参数

3、兄弟通讯:状态提升

  • 把状态放到兄弟组件的共同的父级

4、多层次组件通讯;

  • 逐层传递 :一层一层传递(比较繁琐)
  • Context

1、创建Context ;
2、父组件操作:Provider 共享数据 其中的 value;
3、子组件操作:接收数据 ;分为函数组件 :Consumer / 类组件 : Consumer 和 this.context

具体说说多层次组件通讯

1、props 逐层传递

从上到下,所有的组件都要帮助传递这个 props 到目标位置

props 逐层传递缺点:

  • 操作繁琐
  • 难以维护

index.js

import React from 'react'

import TodoFrom from './TodoFrom'
import TodoContent from './TodoContent'
import TodoState from './TodoState'

class TodoList extends React.Component {
  constructor() {
    // 必须写这句 继承React.Component
    super();
    this.state = {
      datalist: [{
        id: 1,
        target: '按时吃饭',
        done: false,
        addtime: Date.now()
      }, {
        id: 2,
        target: '早睡早起',
        done: false,
        addtime: Date.now()
      }]
    }
    this.addItem = this.addItem.bind(this)
    this.removeItem =this.removeItem.bind(this)
    this.completeItem = this.completeItem.bind(this)
  }
  addItem(target){
    const newData = {
      id:parseInt(Math.random()*10000),
      target,
      done:false,
      addtime:Date.now()
    }
    // 如果上面没有bind改变this指向 则打印不出数据并报错
    console.log(this.state)
    this.setState({
      datalist:[newData, ...this.state.datalist]
    })
  }
  removeItem(id){
    this.setState({
      datalist:this.state.datalist.filter(item => item.id !== id)
    })
  }
  completeItem(id){
    this.setState({
      datalist:this.state.datalist.map(item => {
        if(item.id === id){
          item.done = true
        }
        return item
      })
    })
  }
  render(){
    const {datalist} = this.state
    const total=datalist.length
    const doneLength = datalist.filter(item => item.done).length
    const undoneLength = datalist.filter(item => !item.done).length

    return (
      <div>
        {/* 定义方法并通过props传入子组件 */}
        <TodoFrom addItem={this.addItem}></TodoFrom>
        <TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
        <TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState>
      </div>
    )
  }
}

export default TodoList

TodoForm.js

import React from 'react'

// 类组件
class TodoForm extends React.Component{
  constructor(){
    super()
    this.state={
      keyword:''
    }
    this.changeKeyword=this.changeKeyword.bind(this)
    this.add=this.add.bind(this)
  } 
  changeKeyword(e){
    this.setState({
      keyword:e.target.value
    })
  }
  add(){
    // 类组件 子到父通讯 通过 this.props 执行方法 并传递参数
    this.props.addItem(this.state.keyword)

    this.setState({
      keyword:''
    })
    this.keyword.focus()
  }

  render(){
    return (
      <div>
        <input type="text" value={this.state.keyword} onChange={this.changeKeyword} ref={el=>this.keyword=el}/>
        <button onClick={this.add}>添加</button>
      </div>
    )
  }
}

export default TodoForm

TodoContent.js

import React from 'react'

import TodoItem from './TodoItem'

// 在函数参数中,接收父组件通讯传过来的参数
function TodoContent({datalist,removeItem,completeItem}){
  // 通过传参 接收传递的数据
  // console.log('TodoItem=',datalist) 
  return (
    <table style={{width:'100%',textAlign:'center'}}>
    <thead>
      <tr>
        <th>序号</th>
        <th>待办事项</th>
        <th>是否完成</th>
        <th>操作</th>
        {/* {<th>添加时间</th>} */}
      </tr>
    </thead>

    <tbody>
      {
        // 必须加一个key值
        datalist.map((item,idx)=>(
          <TodoItem 
            index={idx} 
            data={item} 
            key={item.id}
            removeItem={removeItem}
            completeItem={completeItem}
          />
        )
        )
      }
     
    </tbody>
  </table>
  )
}

export default TodoContent

TodoItem.js

import React from 'react'

// 函数组件 仅仅为了展示 没有状态改变
// 在函数参数中,接收父组件通讯传过来的参数
function TodoItem({data,index,removeItem,completeItem}){
  console.log(1)
  console.log('TodoItem=',data,index)
  return (
    <tr>
      <td>{index+1}</td>
      <td>{data.target}</td>
      <td>{data.done?'是':'否'}</td>
      <td>
        <button onClick={completeItem.bind(null,data.id)}>完成</button>
        <button onClick={removeItem.bind(null,data.id)}>删除</button>
      </td>
    </tr>
  )
}

export default TodoItem

TodoState.js

import React from 'react'

// 在函数参数中,接收父组件通讯传过来的参数
function TodoStatue({total,doneLength,undoneLength}){
  // console.log('TodoState=',total,doneLength,undoneLength)
  return (
    <div>
    总数:{total}, 
    完成:{doneLength}, 
    未完成:{undoneLength}
  </div>
  )
}

export default TodoStatue

在这里插入图片描述

2、Context 组件共享

所谓 context,就是上下文环境,某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都能直接访问这个状态,实现步骤如下:

1、创建Context

  const MyContext = React.createContext(defaultValue)

或者新建一个js文件专门存放context

  import React from 'react';

  // 1.创建Context
  const defaultData = {username:'fqniu'}
  const myContext = React.createContext(defaultData);

  export default myContext

2、父组件操作:Provider共享数据

  let data = {username:'fqniu'}
  <MyContext.Provider value={data}>
      <App/>
  </MyContext.Provider>

组件App下的所有子组件都能获取到data数据,如父组件未设置 Provider,子组件接收时得到 defaultValue 的值
其中 value = { 里面是共享的数据 }

3、子组件操作:接收数据

  • 函数组件 :Consumer
  • 类组件:Consumer、this.context
 Consumer:  函数组件和类组件都可以
 
   <MyContext.Consumer>
     {(value) => {
       // 回调函数中获取value值
     }}
   </MyContext.Consumer>
contextType: 只适用于类组件,通过`this.context`获取 , 设置静态属性contextType

  SubComponent.contextType = MyContext;
   
   this.context.username; //fqniu

而这里的TodoItem 组件 如果逐层传递的话 ,比较繁琐,所以使用Context 传递

myContext.js

import React from 'react'

// 1、创建Context 
const defaultData={username:'fqniu'}
const myContext = React.createContext(defaultData)

export default myContext

index.js

import React from 'react'

import TodoFrom from './TodoFrom'
import TodoContent from './TodoContent'
import TodoState from './TodoState'

// 引入Context
import myContext from './myContext'

class TodoList extends React.Component {
  constructor() {
    // 必须写这句 继承React.Component
    super();
    this.state = {
      datalist: [{
        id: 1,
        target: '按时吃饭',
        done: false,
        addtime: Date.now()
      }, {
        id: 2,
        target: '早睡早起',
        done: false,
        addtime: Date.now()
      }]
    }
    this.addItem = this.addItem.bind(this)
    this.removeItem =this.removeItem.bind(this)
    this.completeItem = this.completeItem.bind(this)
  }
  addItem(target){
    const newData = {
      id:parseInt(Math.random()*10000),
      target,
      done:false,
      addtime:Date.now()
    }
    // 如果上面没有bind改变this指向 则打印不出数据并报错
    console.log(this.state)
    this.setState({
      datalist:[newData, ...this.state.datalist]
    })
  }
  removeItem(id){
    this.setState({
      datalist:this.state.datalist.filter(item => item.id !== id)
    })
  }
  completeItem(id){
    this.setState({
      datalist:this.state.datalist.map(item => {
        if(item.id === id){
          item.done = true
        }
        return item
      })
    })
  }
  render(){
    const {datalist} = this.state
    const total=datalist.length
    const doneLength = datalist.filter(item => item.done).length
    const undoneLength = datalist.filter(item => !item.done).length

    return (
      <div>
        {/* 定义方法并通过props传入子组件
        <TodoFrom addItem={this.addItem}></TodoFrom>
        <TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
        <TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState> */}

        {/* Context传递 */}
        <myContext.Provider value={{remove:this.removeItem, complete:this.completeItem}}>
          <TodoFrom addItem={this.addItem}></TodoFrom>
          <TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
          <TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState>
        </myContext.Provider>
      </div>
    )
  }
}

export default TodoList

TodoItem.js

import React from 'react'
import myContext from './myContext'

// 函数组件 仅仅为了展示 没有状态改变
// 在函数参数中,接收组件通讯传过来的参数
function TodoItem({data,index,removeItem,completeItem}){
  console.log(1)
  console.log('TodoItem=',data,index)
  return (
    <tr>
      <td>{index+1}</td>
      <td>{data.target}</td>
      <td>{data.done?'是':'否'}</td>
      <td>
        {/* 逐层传递
        <button onClick={completeItem.bind(null,data.id)}>完成</button>
        <button onClick={removeItem.bind(null,data.id)}>删除</button> */}
        
        {/* Context传递 */}
        <myContext.Consumer>
          {
            ({remove,complete})=>{
              return (
                <React.Fragment>
                  <button onClick={complete.bind(null,data.id)}>完成</button>
                  <button onClick={remove.bind(null,data.id)}>删除</button> 
                </React.Fragment>
              )
            }
          }
        </myContext.Consumer>
      </td>
    </tr>
  )
}

export default TodoItem

注意点

1、 <React.Fragment></React.Fragment> 当嵌套标签时,可以简写 <></>
2、如父组件未设置 Provider,子组件接收时得到 defaultValue的值

修改类组件中class的代码

但是不是感觉那个类组件 class 里面代码要 bind 对自定义方法中this的指向、 还要super(),还有constructor () ,感觉不怎么友好,这时候推荐一个插件来解决此类的问题 : @babel/plugin-proposal-class-properties ,已经配置在webpack.config.js中,不清楚的可以看我上一个博客内容 :基于webpack的react环境配置 中插件配置

index.js
import React from 'react'

import TodoFrom from './TodoFrom'
import TodoContent from './TodoContent'
import TodoState from './TodoState'

// 引入Context
import myContext from './myContext'

class TodoList extends React.Component {
  // constructor() {
  //   // 必须写这句 继承React.Component
  //   super();
  //   this.state = {
  //     datalist: [{
  //       id: 1,
  //       target: '按时吃饭',
  //       done: false,
  //       addtime: Date.now()
  //     }, {
  //       id: 2,
  //       target: '早睡早起',
  //       done: false,
  //       addtime: Date.now()
  //     }]
  //   }
  //   this.addItem = this.addItem.bind(this)
  //   this.removeItem =this.removeItem.bind(this)
  //   this.completeItem = this.completeItem.bind(this)
  // }

  // 添加class插件后写法:
  // 静态属性,就是给类添加属性,如果有静态属性的写法如下:
  static propTypes={}
  static defaultProps ={}

  // 这样就不用 写construct 和 super
  state = {
    datalist: [{
      id: 1,
      target: '按时吃饭',
      done: false,
      addtime: Date.now()
    }, {
      id: 2,
      target: '早睡早起',
      done: false,
      addtime: Date.now()
    }]
  }

  // addItem(target){
  //   const newData = {
  //     id:parseInt(Math.random()*10000),
  //     target,
  //     done:false,
  //     addtime:Date.now()
  //   }
  //   // 如果上面没有bind改变this指向 则打印不出数据并报错
  //   console.log(this.state)
  //   this.setState({
  //     datalist:[newData, ...this.state.datalist]
  //   })
  // }
  // removeItem(id){
  //   this.setState({
  //     datalist:this.state.datalist.filter(item => item.id !== id)
  //   })
  // }
  // completeItem(id){
  //   this.setState({
  //     datalist:this.state.datalist.map(item => {
  //       if(item.id === id){
  //         item.done = true
  //       }
  //       return item
  //     })
  //   })
  // }

  // 箭头函数写法 前提是有安装一个插件 class的
  // 注意:箭头函数的话 无this 指向外面的实例,就不用bind去改变this指向
   addItem = (target) => {
    const newData = {
      id:parseInt(Math.random()*10000),
      target,
      done:false,
      addtime:Date.now()
    }
    // 如果上面没有bind改变this指向 则打印不出数据并报错
    console.log(this.state)
    this.setState({
      datalist:[newData, ...this.state.datalist]
    })
  }
  removeItem = (id) => {
    this.setState({
      datalist:this.state.datalist.filter(item => item.id !== id)
    })
  }
  completeItem = (id) => {
    this.setState({
      datalist:this.state.datalist.map(item => {
        if(item.id === id){
          item.done = true
        }
        return item
      })
    })
  }

  render(){
    const {datalist} = this.state
    const total=datalist.length
    const doneLength = datalist.filter(item => item.done).length
    const undoneLength = datalist.filter(item => !item.done).length

    return (
      <div>
        {/* 定义方法并通过props传入子组件
        <TodoFrom addItem={this.addItem}></TodoFrom>
        <TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
        <TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState> */}

        {/* Context传递 */}
        <myContext.Provider value={{remove:this.removeItem, complete:this.completeItem}}>
          <TodoFrom addItem={this.addItem}></TodoFrom>
          <TodoContent datalist = {datalist} removeItem={this.removeItem} completeItem={this.completeItem}></TodoContent>
          <TodoState total={total} doneLength={doneLength} undoneLength={undoneLength}></TodoState>
        </myContext.Provider>
      </div>
    )
  }
}

export default TodoList
TodoForm.js
import React from 'react'

// 引入Context
import myContext from './myContext'

// 类组件
class TodoForm extends React.Component{
  // constructor(){
  //   super()
  //   this.state={
  //     keyword:''
  //   }
  //   this.changeKeyword=this.changeKeyword.bind(this)
  //   this.add=this.add.bind(this)
  // } 

  // 添加静态属性:等效于TodoForm.xxx = xxx
  // 注意:ES6 支持静态方法,但是不支持静态属性 
  static contextType = myContext
  // static getData(){}

  // 添加实例属性:等效于this.state = {}


  // 添加class插件后写法:
  state={
    keyword:''
  }

  // changeKeyword(e){
  //   this.setState({
  //     keyword:e.target.value
  //   })
  // }
  // add(){
  //   // 执行方法并传递参数
  //   this.props.addItem(this.state.keyword)

  //   this.setState({
  //     keyword:''
  //   })
  //   this.keyword.focus()
  // }
  // changeKeyword(e){
  //   this.setState({
  //     keyword:e.target.value
  //   })
  // }


  // 箭头函数写法 前提是有安装一个插件 class的
  changeKeyword = (e) => {
    this.setState({
      keyword:e.target.value
    })
  }
  add = () => {
    // 子组件:执行方法并传递参数
    this.props.addItem(this.state.keyword)

    this.setState({
      keyword:''
    })
    this.keyword.focus()
  }
 changeKeyword = (e) => {
    this.setState({
      keyword:e.target.value
    })
  }

  render(){
    return (
      <div>
        <input type="text" value={this.state.keyword} onChange={this.changeKeyword} ref={el=>this.keyword=el}/>
        <button onClick={this.add}>添加</button>
      </div>
    )
  }
}

// 添加静态属性
// TodoFrom.contextType = myContext

export default TodoForm

注意点

1、箭头函数的话 无this 指向外面的实例,就不用bind去改变this指向
2、ES6 支持静态方法,但是不支持静态属性 ,这里能用静态属性的原因是关于自定义方法使用箭头函数的插件 @babel/plugin-proposal-class-properties

react基础

1、React介绍

React 官网地址:http://facebook.github.io/react/

  • 介绍

    React 是一个视图层的框架,起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在 2013 年 5 月开源了。由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。

  • 特点

    • 声明式设计与函数式编程

      声明式编程与命令式编程。

      • 声明式:我要做什么,注重结果
      • 命令式:我要怎么做,注重过程
      • 函数式编程:利用函数参数、返回值等去实现功能的编程方式
    • 高性能

      React 通过对 DOM 的模拟(Virtual DOM),并配合 diff 算法,最大限度地减少与 DOM 的交互,从而提升性能。

    • 组件化开发

      通过 React 构建组件,使得代码更加容易得到复用,能够高效率的应用在大项目的开发中。

    • 单向响应的数据流

      React 中的数据是单向自顶向下传递的,父组件数据的更新会自动传递到子组件,但子组件的数据更新不会影响到父组件,也不能在子组件修改父组件传入的数据

    • JSX 扩展

      JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。

2. 使用 React

  1. script 标签引入

    • react.js React 的核心库

    • react-dom.js 提供与 DOM 相关的功能

    • browser.js babel 针对于浏览器环境的版本,可将 JSX、ES6+等语法编译成浏览器支持的代码

      在浏览器中使用 Babel 来编译 JSX 为实时编译, 效率是非常低的,一般只用于演示

  2. 使用

    • 创建虚拟节点:React.createElement(type,props,children)

      • type: 节点名称
      • props: 节点属性
      • children: 节点内容
    • 渲染节点:ReactDOM.render(VNode,target)

      是 ReactDOM 的最基本方法,用于将内容渲染到指定节点中

      • VNode:虚拟节点或 React 组件
      • target:挂载点,必须为元素节点
    <script>
         ReactDOM.render(
             React.createElement("div", { className: "container" }, "创建虚拟节点"),
             document.getElementById("app")
         );
    </script>
    

3. JSX 语法

一种特殊的 js 语法,是 ECMAScript 的扩展,可以让我们在 js 代码中使用 html 标签来编写结构,避免繁琐的React.createElement()操作

PS:JSX 是React.createElement()的语法糖,虽然看起来像 HTML,但严格意义上来说它还是 JS,这种特殊的 JS 浏览器无法识别,所以需要 babel 进行编辑,编辑后它最终会转化成 React.createElement()去创建 js 对象。

<script type="text/babel">
  ReactDOM.render(
    React.createElement("div", { className: "container" }, "创建虚拟节点"),
    document.getElementById("app")
  );
</script>

注意:使用 JSX 需要遵循以下规则

  • 因为 Javascript 代码与 JSX 代码并不兼容,凡是使用 JSX 的 script 标签都需要加上 type="text/babel"

  • 在 jsx 代码中,同为 js 关键字的 html 属性不能直接使用

    • class -> className,
    • for -> htmlFor
  • 属性使用驼峰

    • tabindex -> tabIndex
    • autofocus -> autoFocus
    • onkeydown -> onKeyDown
  • 必须结束标签

        <input type="text" />
        <img src="logo.png" />
    
  • style 属性的值接收一个对象,css 的属性必须为驼峰写法

      <div style={{backgroundColor:"#f60"}}
    
  • 花括号{}内为 js 表达式,不允许出现 var,let,const 等关键字

  • 使用 js 语法注释(如{/*注释内容*/},//注释内容

4. React 组件

所谓组件,即封装起来的具有独立功能的 UI 部件,用来组合成更高级东西的物件,通俗来讲,就是创建一个元素,组件有以下规范:

  • 组件名必须大写开头
  • 只能包含一个顶层标签

在这里插入图片描述

PS:在开发过程中,要善于观察和抽象。尤其是在项目前期,不要着急写代码,一定要观察项目的原型图或者设计稿,弄懂哪些部分是可以拆分成复用的公共组件的。这样做能让你后面的工作,事半功倍

组件的类型与定义

  • 函数组件
  • 类组件

搞懂组件的定义与使用后,更重要的是要学会根据需求组合或拆分更大或更细的组件

函数组件(无状态组件、UI 组件)

是一个纯函数,只用组件展示,组件只负责根据外部传入的 props 来展示,书写更简洁,执行效率更高(推荐)

    //定义
    function MyComponent(){
        return <h1>函数组件</h1>
    }

    //使用
    <MyComponent />
    <MyComponent></MyComponent>
类组件(状态组件、容器组件)

类组件有更丰富的特性,如:state 状态、生命周期、this 等

// 定义
class About extends React.Component {
  render() {
    return <div className="box">类组件</div>;
  }
}

// 使用
<About />;

5、react 组件的数据挂载方式

1. state:适用于类组件

React 类组件拥有自己的状态 state,state 状态改变时自动刷新组件(执行组件中的 render 方法)

  • 初始 state
class MyComponent extends React.Component {
  constructor() {
    super(); // 这行代码不能少
    this.state = {
      num: 1,
    };
  }
}
  • 修改状态:setState()

    • 直接修改:setState(nextState[,callback])

      • nextState: 将要设置的新状态,该状态会和当前的 state 合并
      • callback: 可选参数,回调函数。该函数会在 setState 设置成功,且组件重新渲染后调用。
      this.setState({
        num: 10,
      });
      
    • 依赖上次setState()的结果:setState(fn [,callback])

      • fn(prevState)
      this.setState((prevState) => {
        return { num: prevState.num + 1 };
      });
      
  • 注意事项

    • setState()是异步的

      PS:调用 setState()并不会马上修改 state。而是进入到一个更新队列里面,所以不能在组件内部通过this.state.xx=xx直接修改状态,因为修改后会被队列中的 setState()替换(如下两次输出都为 false)

    • 多次setState()会自动合并

      React 内部自动进行 state 的对比,得到最终结果后才渲染视图,所以并不需要担心多次进行 setState() 会带来性能问题

    console.log(this.state.num); //1
    this.setState({
      num: 10,
    });
    console.log(this.state.num); //1
    
    • setState()会自动刷新组件,也可以利用forceUpdate()进行手动强制刷新

2. props:类组件与函数组件都适用

  • 函数组件:通过函数的第一个参数访问
  • 类组件:通过this.props访问
  • 普通属性
<MyComponent data={100} />
  • Render Props

    使用一个值为函数的 prop 共享代码的简单技术(类似于 Vue 中的作用域插槽

        // Vue的实现
        // <mycomponent>
        //     <template v-slot:header="data">
        //         <header>头部标题{{data}}</header>
        //     </template>
        // </mycomponent>
    
        <MyComponent render={(data) => <header>头部标题{data}</header>} />
    
  • children

    <Button>点我有惊喜</Button>

    // Button组件内获取通过props.children获取"点我有惊喜"
    function Button(){
        return <button>{props.children}<button>
    }
props 类型校验

给组件设置静态属性 propTypes 来设置组件各个属性的类型检查器, 用于限制传入属性的数据类型,在编写组件时比较有用

  • React 内置数据类型检查器 PropTypes

    在 React 16 版本之后, PropTypes 从 react 包 分离到了 prop-types 包中
    需要npm i prop-types 安装

    import PropTypes from "prop-types";
    MyComponent.propTypes = {
        name: PropTypes.string,
    };
  • 自定义属性校验器
MyComponent.propTypes = {
  //自定义验证规则
  age: (props, propName, comName) => {
    if (props[propName] < 18) {
      return new Error(propName + "必须大于等于18岁");
    }
  },
};

3. 条件渲染:三元运算

{
    login ? 
    <button>退出</button>
    :
    <button>登录</button>
}

4. 列表循环

使用数组的 map() 方法来创建列表,可配合filter()进行过滤操作

  • key

    react利用key来区分组件的

    • key相同,表示同一个组件,react不会重新销毁创建组件实例,只可能更新;
    • key不同,react会销毁已有的组件实例,重新创建组件新的实例
    • key应该是稳定唯一的,尽量不要用数组的索引index作为key(排序或添加时索引值会改变)

5. 事件处理

事件名称采用驼峰式写法(如:onClick,onKeyDown)

event对象与传参

事件处理函数的最后一个参数,保存事件发生时的信息

   //普通使用
    clickHandle(e){
        console.log(e);
    }
    <button onClick={this.clickHandle}>按钮</button>

    // bind方式
    clickHandle(num1,num2,e){
        console.log(num1,num2,e);
    }
    <button onClick={this.clickHandle.bind(this,10,20)}>按钮</button>

    // 箭头函数
    clickHandle(e,num){
        console.log(e,num);
    }
    <button onClick={e=>this.clickHandle(e,10)}>按钮</button>
事件处理函数中的this指向

默认情况下,事件处理函数没有this指向(值为undefined),可通过以下方式改变this指向

    clickHandle(){
        console.log(this);//undefined
    }
    <button onClick={this.clickHandle}>按钮</button>
  • bind方法

    • 初始化时bind (推荐)
    • 执行时bind
        class MyComponent extends React.Component{
            constructor(props){
                super(props);
                this.clickHandle = this.clickHandle.bind(this)
            }
            clickHandle(){
    
            }
        }
    
  • 使用箭头函数

    • 定义时使用箭头函数
        class MyComponent extends React.Component{
            // 需要插件支持:@babel/plugin-proposal-class-properties
            clickHandle = ()=>{
                
            }
        }
    
    • render中使用箭头函数
        <button onClick={()=>{}}>按钮</button>
    

6. refs

受控组件与非受控组件

  • 应用位置

    • 应用在元素节点上:对节点的引用
    • 应用在组件上:对组件实例的引用

    函数组件不可使用ref

  • 适合使用 refs 的情况:

    • 管理焦点,文本选择或媒体播放
    • 触发强制动画
    • 集成第三方 DOM 库
  • 设置方式

    • React.createRef()

    • 回调 Refs (推荐)

      ref={el=>this.myRef=el}

    // React.createRef()
    this.btnSave = React.createRef();
    <button ref={this.btnSave}>保存</button>
    //获取节点
    this.btnSave.current

    //回调 Refs
    <button ref={el => {this.btnSave = el}}>保存</button>
    // 获取节点
    this.btnSave

7. 显示html内容

    // htmlString为带html标签的内容
    <div dangerouslySetInnerHTML={{ __html: htmlString}}></div>

6、组件通讯

1. 父子通讯:props

2. 兄弟组件通讯:状态提升

把状态放到两个组件共同的父级

3. 多层次组件通讯

  • props 逐层传递

    从上到下,所有的组件都要帮助传递这个 props 到目标位置

    • 缺点:
      • 操作繁琐
      • 难以维护
  • context 组件共享

    所谓 context,就是上下文环境,某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都能直接访问这个状态,实现步骤如下:

    1. 创建 Context:

      let defaultValue = { username: "laoxie" };
      let MyContext = React.createContext(defaultValue);
      
    2. 父组件 Provider

          let data = {username:'jingjing'}
          <MyContext.Provider value={data}>
              <App/>
          </MyContext.Provider>
      

      组件App下的所有子组件都能获取到data数据,如父组件未设置 Provider,子组件接收时得到 defaultValue 的值

    3. 子组件接收

      • contextType

    只适用于类组件,通过this.context获取

    SubComponent.contextType = MyContext;
    
    this.context.username; //jingjing
    
  • Consumer

    <MyContext.Consumer>
      {(value) => {
        // 回调函数中获取value值
      }}
    </MyContext.Consumer>
    
    
    

7、高阶组件 HOC(High Order Component)

理解 HOC

  • 高阶组件是一个纯函数
  • 高阶组件的参数为组件,返回值为新组件
  • 高阶组件是一种设计模式,类似于装饰器模式

定义HOC

  • 定义方式一: 属性代理

    作用: 提取公共部分,向下传输 props

//utils/withStorage.js
import React, { Component } from "react";
const withStorage = (WrappedComponent) => {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem("data");
      this.setState({ data });
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
};
export default withStorage;
  • 定义方式一: 反向继承

    作用: 拦截生命周期、state、渲染过程等

import React, { Component } from "react";
const withStorage = (WrappedComponent) => {
  return class extends WrappedComponent {
    componentWillMount() {
      let data = localStorage.getItem("data");
      this.setState({ data });

      // 调用父类生命周期函数,使之不被覆盖
      super.componentWillMount();
    }

    render() {
      // 调用父类render方法实现渲染
      return super.render();
    }
  };
};
export default withStorage;

在组件中使用 HOC

//components/Home.js
import React, { Component } from "react";
import withStorage from "../utils/withStorage";

class Home extends Component {
  render() {
    //通过高阶组件可以直接获取data
    return <h2>{this.props.data}</h2>;
  }
}
export default withStorage(Home);
  • ES7 装饰器写法: @
import React, { Component } from "react";
import withStorage from ".../utils/withStorage";

@withStorage
class Home extends Component {
  render() {
    return <h2>{this.props.data}</h2>;
  }
}

export default Home;

PS:目前浏览器不支持 ES7 装饰器,需安装@babel/plugin-proposal-decorators插件

8、组件生命周期

组件的生命周期分成四个状态:

  • Initial: 初始化阶段
  • Mounting:挂载阶段
  • Updating:更新阶段
  • Unmounting:卸载阶段
  • 特殊生命周期函数

在这里插入图片描述

补充一点

场景如下:假如在 componentDidMount() 里面发ajax请求的话,如果更新数据后,不会自己再发请求了,因为页面加载只执行一次 componentDidMount() ,因为更新在componentDidUpdate() 里面;

钩子函数

  • componentWillMount (不推荐,V17.x版本中将移除)

在组件被渲染到页面上之前执行

  • componentDidMount

    组件被渲染到页面上后立马执行

    • 这个时候是做如下操作的好时机:
      • 某些依赖组件 DOM 节点的操作
      • 发起ajax请求
      • 设置 setInterval、setTimeout 等计时器操作
      • 读取本地存储数据
  • componentWillUpdate(nextProps, nextState) (不推荐,V17.x版本中将移除)

    在初始化时不会被调用,可能在以下两种情况下被调用:

    • 当组件 shouldComponentUpdate 返回 true 且接收到新的props或者state但还没有render时被调用
    • 调用 forceUpdate 时将触发此函数
  • componentDidUpdate(prevProps, prevState)

    在组件完成更新后立即调用。在初始化时不会被调用。

    • 在此处是做这些事情的好时机:

      • 执行依赖新 DOM 节点的操作。
      • 依据新的属性发起新的ajax请求。

      注意:一定要在确认属性变化后再发起ajax请求,否则极有可能进入死循环:DidUpdate -> ajax -> changeState -> DidUpdate -> …)

  • componentWillUnmount

    在组件从 DOM 中移除之前立刻被调用。

    • 此处最适合做以下操作 为了性能优化,防止内存泄漏
      • 清除定时器
      • 终止ajax请求
  • componentWillReceiveProps(nextProps) (不推荐,V17.x版本中将移除)

    该方法在以下两种情况下被调用:

    • 组件接收到了新的props属性。新的属性会通过 nextProps 获取到。
    • 组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。
  • shouldComponentUpdate(nextProps, nextState)

    在props改变或state改变时被调用,必须返回true或false来决定给是否重新渲染组件

    • 在初始化时或者使用forceUpdate时不被调用。
    • 一般用于性能优化
         //在render函数调用前判断:如果前后state中num不变,通过return false阻止render调用
        shouldComponentUpdate(nextProps,nextState){
            if(nextState.num == this.state.num){
                return false
            }
        }
    

    PS:这是一个询问式的生命周期函数,如果返回true,组件将触发重新渲染过程,如果返回false 组件将不会触发重新渲染。因此,合理地利用该函数可以一定程度节省开销,提高系统的性能

钩子函数执行顺序如下:
1、进入页面:
componentWillMount——> render ——> componentDidMount

2、更新数据后:
shouldComponetUpdate——>componentWillMount ——> render ——>componentDidMount

3、切换组件后:
componentWillUnmount

react 中性能优化的方案

  • shouldComponentUpdate
  • PureComponent
    React.Component的区别是PureComponent内部帮我们做了shouldComponentUpdate的简单判断,让state和props不变的情况下自动return false,从而实现性能优化。
    注意:用 React.PureComponent(),但是里面不能再写shouldComponentUpdate()会报错。

补充如下:

import React from 'react'

class Lifecycle extends React.Component{
// 如果用React.PureComponent 是不用加 shouldComponentUpdate,加了会报错
// 内部已经做了简单的处理了优化,后面用这种比较多
// class Lifecycle extends React.PureComponent{
  constructor(){
    super()
    this.state={
      qty:0
    }
  }
  // 生命周期函数
  // 1、挂载阶段
  componentWillMount(){
    console.log('componentWillMount')
  }
  componentDidMount(){
    console.log('componentDidMount')
  }

   // 2、更新阶段
  componentWillUpdate(nextProps, nextState){
    // this.props, this.state.qty:1 / nextState.qty:2
    console.log('componentWillUpdate');
  }
  componentDidUpdate(nextProps, nextState){
    // this.props, this.state.qty:2 / nextState.qty:1
    console.log('componentDidUpdate');
    // 注意:在这里如果有setState时,一定要加条件,不然会造成死循环
    // 因为生命周期中,如果这里数据更新,会render,会有一个闭环,具体看生命周期图解
  }

  // 3、卸载阶段
  componentWillUnmount(){
    console.log('componentWillUnmount');
  }

  /**
   * 组件接收到了新的props属性。新的属性会通过 nextProps 获取到。
   * 组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。
   */
  componentWillReceiveProps(){
    console.log('componentWillReceiveProps');
  }

  // 在props改变或state改变时被调用,
  // 必须返回true或false来决定给是否重新渲染组件
  shouldComponentUpdate(nextProps,nextState){
    // console.log(this.state.qty) //当前值
    // console.log(nextState.qty)  // 将要改变的值
    console.log('shouldComponentUpdate')
    // return true

    // 只有当qty是5的倍数时, 才继续render, 页面更新数据, 不然不执行render
    // if(nextState.qty % 5 === 0){
    //   return true
    // }else{
    //   return false
    // }

    // 只有当将要改变的值 等于传递参数的值时,再让后面的render重新执行
    // 避免重新渲染,这里可以做优化部分,只有传递的参数变化时,才渲染render
    // 而此时age的变化,不想让下面的组件重新render,所以加下面的条件判断;
    if(nextProps.username === this.props.username){
      return false
    }
    return true
  }

  render(){
    console.log('render')
    return (
      <div>
        <h1>生命周期测试</h1>
        <button onClick={()=>{
          this.setState({
            qty:this.state.qty + 1
          })
        }}>点我 {this.state.qty}</button>
      </div>
    )
  }
}

export default Lifecycle
import React from 'react'

import Lifecycle from '../components/Lifecycle'

// 有状态改变用class组件
class App extends React.Component {
  state = {
    show: true,
    username:'fqniu',
    age:18
  }
  render(){
    return (
      <div>
        {
          this.state.show 
          ? <Lifecycle username={this.state.username}/>
          : <div>我是div</div>
        }
        <button onClick={()=>{
          this.setState({
            show:!this.state.show
          })
        }}>App按钮: 点我切换显示</button>

        <button onClick={()=>{
          this.setState({
            username:this.state.username+'plus'
          })
        }}>修改username:{this.state.username}</button>

        <button onClick={()=>{
          this.setState({
            age:this.state.age+1
          })
        }}>修改age:{this.state.age}</button>
      </div>
    )
  }
}

export default App

注意一点:在constructor中打印this是有参数的,虽然this里面存在有props,但是打印 this.props 则返回undefined 。如果你想要拿到这个this.props的话,则需要下面的写法:

  constructor(props){
    super(props)
    this.state={
      qty:0
    }
  }

console.log(this)
// 这样就可以拿到 this.props 的值了。
console.log(this.props)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值