react---上篇

react(上)

主流函数组件,但是类组价也要用,都要会

  • 特点:
  • 声名式: 就像写 HTML一样,react负责渲染UI , 数据的声明
  • 基于组件开发,可以保持状态和DOM的分离
  • web, 小程序都可用

简单使用

  • 简单应用不适用脚手架:

  • 1,进入文件

  • 创建 HTML, 引入配置文件,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0hMiW7v-1690027946541)(C:\Users\30641\AppData\Roaming\Typora\typora-user-images\image-20230710210149417.png)]

  • 注意 引入的顺序不能颠倒,1) react.js 是react的核心库文件,此文件用来穿件虚拟DOM,已经相关的方法, 2) react-dom.js 将虚拟dom转为真实dom,解析出来,渲染到页面中

  • 2,指定挂载节点

  • <div id="root"></div>
    
  • 3, 编写业务

  • <script>
        let rooDom = documnet.getElementById("root")
        // 创建一个vnode对象
    	let vnode = React.createElement(
        'h3', // 要创建的标签名
        {}/null,  // 标签的属性,可以为空对象,或者null
         '你好react,标签的内容'
        )    
    
    
       // 解析vnode为真实dom,并渲染到页面中
    1, ReactDOM.render()  // 在react18之前使用,18也支持,但是不支持渲染新特性
    
    // createRoot(挂载节点)
    // render(渲染组件)
    2, ReactDOM.createRoot(rootDom).render(vonde)
    
    </script>
    
  • 4,小案例

  •  const rootDom = document.getElementById("root")
       
            let el = React.createElement
       
            let vnode = el(
                'ul', // 标签名
                null, //  标签属性,可以是空对象或者是null ,如果写style样式,只能写对象
                //  事件一定要用小驼峰命名法,onClick
                el("li" , null , 
                    el("span" , {title:"aaa" , style:{color:'red' }} , "张三"),
                    el("button" , null , "查看"),
                    el("button" , {onClick: ()=> console.log(123)} , "删除")
                ),
                el("li" , null , 
                    el("span" , null , "框死"),
                    el("button" , null , "查看"),
                    el("button" , null , "删除")
                ),
                el("li" , null , 
                    el("span" , null , "王五"),
                    el("button" , null , "查看"),
                    el("button" , null , "删除")
                ),
            )
           
            ReactDOM.createRoot(rootDom).render(vnode)
    
  • 非常的繁琐且不直观,所以引入 jsx

jsx

jsx 编译

 <!--  jsx编译为js -->
<script src="./js/babel.js"></script>


 <!-- 3,编写业务 -->
    <!-- 如果用到了jsx,则在浏览器直接运行HTML文件时,一定要设置,script的type -->
    <!-- type 不同,浏览器对应的工作性质也不一样 -->
    <script type="text/babel">
        const rootDom = document.getElementById("root")

     // 创建得到一个 vnode对象,jsx 是js的增强版本 , 他对字符窜进行了增强
     //  jsx 在写HTML时一定要符合xml规范
    //  xml规范,严格标识语言,属性名要小写且用引号括起来,标签必须为双标签,又开始和闭合
    //  jsx要求,内置html标签名为小写,自定义组件名为首字母大写,属性名称为小写且只要用引号括起来,要有结束和开始
 //  jsx不能直接解析运行,所以要先将jsx转为js ====》 通过引入的 babel 库来完成对js的编译
   // 可以吧他理解为一个组件中的UI部分
        const vnode = (
            <ul>
                <li>
                    <span>张三</span>
                    <button>查看</button>
                </li>
                <li>
                    <span>张无</span>
                    <button>查看</button>
                </li>
            </ul>
        )

        ReactDOM.createRoot(rootDom).render(vnode)
</script>

变量绑定

        const username = "张三aaa"

        const fn = function () {
            console.log("aaa")
        }

        const age = 22

        const vnode = (
            <div>
                {/*绑定变量,{变量}*/}
                <div>{username}</div>

                {/* 表达式*/}
                <div>{1 + 3}</div>

                {/* 调用内置方法*/}
                <div>{username.substr(0, 3)}</div>

                {/* 调用自定义函数 */}
                <div>{fn()}</div>

                {/*不支持声明变量,if语句,while等块级语句*/}
                {/*支持三目运算,他很重要*/}
                <div>{age < 20 ? <h3>而已</h3> : <h3>老人</h3>} </div>
            </div>
        )

动态属性


脚手架

// 安装webpack切记不要全局安装
npm i -D webpack webpack-cli webpack-dev-server ...
npm i -S react react-dom

// 创建一个react项目
npx create-react-app 项目名

// 新建webpack配置文件
webpack.config.js
//	五个核心的配置
- mode , -entry ,-output , -module:rules  ,-plugins
//  工程化后的REact目录
public   index.html
src   App.js   index.js

组件

函数组件

//  函数组件
//  本质上还是一个函数
//   1, 函数名称首字母大写。jsx要求自定义组件在调用时首字母要大写
//   2, 他会有一个形参,可以接受任意类型的数据,可以用它来控件自父组件数据
//  3, 返回必须包括一个jsx , 且jsx必须有顶层元素包裹,还可以返回null==》 r18之后
// 4, 定义的函数一定要默认导出   

// rfc 快捷键生成
import React from 'react';

const 01 = () => {
 return (
   <div>
     	// 导出的内容
   </div>
 );
}

export default 01;

类组件

import React from 'react';
// 定义一个类组件
//  1, 只能使用es6的类来定以 , 类名必须首字母大写
// 2, 必须继承一个react.component父类
// 3 , 此类中必须重写render 方法, 此方法一定要返回一个 jsx/null / undefined 
//  4, 此类要导出

// 用  rcc  来快捷创建
class App extends React.Component{
  render(){
    return (
      <div>
        <h2>jjjjsdfasdf</h2>
      </div>
    )
  }
}

export default App;
// 函数具有二义性,函数,类都可以
function Person(){
 //  用new。target来禁用函数,他不能被new,只能当成函数来调用
 if(new.target){
   throw new Error("错误")
 }
}

console.log( new Person())  // 报错

props—父子间传值

函数组件的父向子传值

import React from 'react';

//  子组件接收父组件传过来的数据 , 通过函数的形参来接受
//  形参可以接受任意类型的数据得到的是一个对象
const Child = props => {
  console.log(props)
    props.title = "aaa"   // 直接报错,props是一个只读的属性,不能被修改,要想修改只能是父组件来完成,是一个单项数据,父流向子
  return (
    <div>
      <h5>child 组件 --- {props.title}</h5>
    </div>
  );
}

// props 是一个对对象  可以结构赋值,运用更加的简单
const Child ={title = "解构时可以赋予初始值"} =>{
	return (
   		<div>
       	  <div> {title} </div> 
       </div> 
    )
}

const App = () => {
  const title = "我是父朱健传过来的值"
  return (
    <div>
      <h3>App组件</h3>
      {/*
        父组件通过自定义属性来向子组件传递数据,props
        调用子组件,直接将子组件类名当标签名用
      */}
     < Child  title={title}/>

    </div>
  );
}

export default App;

类组件的父向子传值

import React, { Component } from 'react';

class Child extends Component {
  render() {
    //  类组件中子组件通过在成员属性  this.props中获取父组件传过来的自定义属性值
    //  this.props 他的值为一个对象, 也不可修改
    //  const 定义一个不可改变的常量
    const { title } = this.props
    return (
      <div>
        <h3>child --- {title} </h3>
      </div>
    );
  }
}

class App extends Component {
  // 成员属性
  title = "我是父组件传过来的值"
  render() {
    return (
      <div>
        <h3>app</h3>
        {/*
          父组件向子组件传值时,通过自定义属性来完成,props传任意类型的值
        */}
        <Child title={this.title} />
      </div>
    );
  }
}

export default App;


事件处理

事件绑定(函数)

import React from 'react';

const App = () => {
  return (
    <div>
      {/*
      1, 事件名要用下驼峰
      2, 绑定的事件方法不要写小括号
      3 如果要写小括号,绑定的一定要是一个函数体
    */}
      <button onClick={clickHandler}>点击事件</button>
      <button onClick={clickHandlerw()}>点击事件</button>
    </div>
  )

  function clickHandler() {
    console.log(111)
  }
}
// 写在下面一定要写function,下载return上面可以用箭头函数
// 柯理化函数,函数返回函数
// function clickHandlerw(){
//   return function(){
//     console.log(111)
//   }
// }

// 推荐写法:
const clickHandlerw = () => () => {
  console.log("2222")
}

export default App;


事件绑定(类)

import React, { Component } from 'react';

class App extends Component {
  // 正常用,调用时不加小括号
  // clickHandler(){
  //   console.log("111")
  // }
  //  加小括号调用
  // clickHandler(){
  //   return () => {
  //     console.log("222")
  //   }
  // }

  count = 100
  clickHandler() {
    console.log(this.count)
  }

  render() {
    return (
      <div>
        {/*
        类组件中会有this指向问题,用bind  不会立即执行
      */}
        <button onClick={this.clickHandler.bind(this)}>点击事件</button>
      </div>
    );
  }
}

export default App;

合成事件

  • 为什么出现这个技术?

**性能优化:**使用事件代理统一接收原生事件的触发,从而可以使得真实 DOM 上不用绑定事件。React 挟持事件触发可以知道用户触发了什么事件,是通过什么原生事件调用的真实事件。这样可以通过对原生事件的优先级定义进而确定真实事件的优先级,再进而可以确定真实事件内触发的更新是什么优先级,最终可以决定对应的更新应该在什么时机更新。

**分层设计:**解决跨平台问题,抹平浏览器差异。

如何解释合成事件一定不在挂载点当中,但全部打印后,合成事件的捕获阶段先打印,说明挂载点已经被劫持,

但因顺序为 : body–》 顶层元素合成 — 》 元素合成 —》 挂载点原生 —》 元素原生 —》 元素合成冒 —》 顶层合成冒 —》 挂载原冒 —》 bogy冒

import React, { Component } from 'react';

class App extends Component {

  // 合成事件  r17之前只对冒泡做劫持,捕获阶段不处理,18之后才处理
  //  冒泡阶段
  click() {
    console.log("合成-冒泡-click")
  }

  //  捕获阶段
  clickCapture() {
    console.log("合成-捕获-clickCapture")
  }

  // 生命周期方法,类似与vue中的mounted,虚拟dom编译为真实的dom,挂载到节点后执行
  componentDidMount() {

    //  一下皆是原生事件
    document.getElementById("btn").addEventListener("click",
      () => {
        console.log("原生 - 捕获 -click1")
      },
      true  // 表示捕获阶段执行
    )
    document.getElementById("btn").addEventListener("click",
      () => {
        console.log("原生 - 冒泡click2")
      },
      false  // 表示冒泡阶段执行
    )


    document.getElementById("root").addEventListener("click",
      () => {
        console.log("root - 捕获 -click1")
      },
      true  // 表示捕获阶段执行
    )
    document.getElementById("root").addEventListener("click",
      () => {
        console.log("root - 冒泡click2")
      },
      false  // 表示冒泡阶段执行
    )
  

  document.body.addEventListener("click",
  () => {
    console.log("body - 捕获 -click1")
  },
  true  // 表示捕获阶段执行
)
document.body.addEventListener("click",
  () => {
    console.log("body - 冒泡click2")
  },
  false  // 表示冒泡阶段执行
)
}

  //  事件的执行顺序从:body捕获---》  合成捕获 --》root捕获---》 原生事件捕获 ---》 原生冒泡  ---》 合成冒泡  ---》 root冒泡  ---》 body冒泡
  render() {
    return (
      <div>
        <button id='btn' onClick={this.click} onClickCapture={this.clickCapture}>点击事件</button>
      </div>
    );
  }
}

export default App;

阻止事件传播

			 捕获阶段  root  ---  son
         document.getElementById("root").addEventListener("click" , ()=>{
             console.log("root -- 1")
         } , true ) 
         document.getElementById("son").addEventListener("click" , ()=>{
             console.log("son -- 1")
         } , true ) 


        //  冒泡阶段  son -- root
        document.getElementById("root").addEventListener("click" , ()=>{
            console.log("root -- 1")
        } , false ) 
        document.getElementById("son").addEventListener("click" , ()=>{
            //  阻止事件继续传递 , 但当前绑定事件的元素会继续执行
            // event.stopPropagation()
     //  阻止事件继续传递,并且还会阻止当前元素中其他的未执行函数,当前的事件执行
            event.stopImmediatePropagation()
            console.log("son -- 1")
        } , false ) 
        document.getElementById("son").addEventListener("click" , ()=>{
            console.log("son -- 2")
        } , false ) 
        document.getElementById("son").addEventListener("click" , ()=>{
            console.log("son -- 3")
        } , false ) 

网络请求

/api/v1/getNowPlayingFilmList?cityId=110100&pageNum=1&pageSize=10 电影网站的数据地址

  • 安装插件: npm i -S axios
 解决跨的方案: 浏览器同源策略的问题,
// cors: 在h5 之后刘篮球对跨域也进行了修订,只要你的响应头中有规定的信息则允许跨域
//  主流,cors. 利用响应头中的信息,让浏览器允许网络跨域 jsonp , 代理,利用HTML标签,websocket ,     postmessage      window.domain
 
 react 的代理实现跨域问题: ==》 在src文件下,设置  setupProxy.js 文件
 并通过安装 http-proxy-middleware ==>   `npm i -D http-proxy-middleware`
 
 //  结构的同时起一个别名,为了调用的时候方便: 
const {createProxyMiddleware:proxy}  = require('http-proxy-middleware')

//  app 对象,他是一个Expres实例对象
module.exports = app =>{
    // 代理,一API开头就走代理
    app.use(
        '/api',
        proxy({
            target:"https://api.iynn.cn/film",
            changeOrigin:true ,
            pathRewrite:{
                // 充新定义
            }
        })
    )

    // app.get('api/aa' , (req,res) => {
    //     res.send({
    //         code: 0,
    //         msg: "ok",
    //         data:1
    //     })
    // })
}

网络请求数据更新到视图

// 父组件
import React, { Component } from 'react';

// 导入api方法,也就是获取到里面的值
import { getNowPlayingFilmApi } from './api/filmApi'
//  导入子组件
import Loading from './components/Loading';

class App extends Component {

  state = {
    films: [],
    total: 0
  }

  async componentDidMount() {
    let {films , total} = await getNowPlayingFilmApi()
    this.setState({
      films,
      total
    })
  }

  render() {
    const { films } = this.state
    return (
      <div>
        <h3>app组件</h3>
        <ul>{
          films.length === 0 ?
       	// child 插槽
           ( <Loading >
              <h1>loading...</h1>
            </Loading>) :
            (films.map(({ filmId, name }) => <li key={filmId}> { name } </li> ))   *注意只有一个{} ,区别vue的双花括号*
        }</ul>
      </div>
    );
  }
}

export default App;



// 子组件
import React, { Component } from 'react';

class Loading extends Component {
    render() {
        const { child } = this.props
        return (
            <div>
 //示例中的用法是将

loading...

作为child传递给组件。这样,在Loading组件中,如果child存在,它将渲染child,否则会显示默认的"加载中。。。"文本(

加载中。。。

)。
{child ?? <h3>加载中。。。</h3>} </div> ); } } export default Loading;

父子间传值

props实现

  1. 实现父子之间的传值,事件上都是父组件向子组件传递数据和方法, 子在接受了付的数据后,用父组件传过来的方法修改接受的值,父组件中对对应的值也就会随着改变 如果是子自己的值怎么传给父?
import React, { Component } from 'react';
// 限制值得类型
import { number,func } from 'prop-types'

class App extends Component {
 state ={
   count : 1000
 }
 
addCount = n =>{
 this.setState(state => ({
   count: state.count + n 
 }))
}

 render() {
   const {count} = this.state
   return (
     <div>
         <h3>父组件 ---{count}</h3>
         <hr />
         {/* 父向子传值 通过自定义属性 Props传值,他是一个单向数据流 */}
         <Child count={count} addCount={this.addCount} />    
     </div>
   );
 }
}

class Child extends Component {
  //  限制类型
  static propTypes ={
   count:number,
   addCount: func
 }

 setCount =()=>{
   // 子相父传值  vue: emit 
   // 在react中,可以让父修改当前的数据的方法也传过来,然后再子组件中调用 ,
   this.props.addCount(1)
 }
    
 render() {
   const { count } = this.props   //自接受父传来的值
   return (
     <div>
        <h3>child组件 -- {count}</h3> 
        <button onClick={this.setCount}>子组件修改count值</button>
     </div>
   );
 }
}

export default App;

ref实现

import React, { Component, createRef } from 'react';

class Child extends Component {
 state = {
   count: 100
 }
 
     //  子组件中的修改数据方法,可以再绑定ref后在父组件中通过  this.childInstance(属性名).current.setCount(10) 来调用
   setCount = (n , cb) => {
   this.setState(state =>({
       count : state.count + n
       }) , ()=>{
          cb(this.state)  // 用来让父组件同步的获取当前的state中的值
       })
        //  在修改数据的同时还可以向父组件传别的值
         return 200
     }
     render() {
     const { count } = this.state
   return (
       <div>
           <h3>子组件 ---{count}  </h3>
            
          </div>
        );
     }
 }
class App extends Component {

 childRef = createRef()

   getChildInstance = () => {
   console.log(this.childRef.current)  // 获得子组件实例
     // let n =this.childRef.current.setCount(10)  // 就收子组件传过来的值
       // console.log(n , this.childRef.current.state.count) //获取子组件的值,但得到的是修改前的一次,因为是异步的
   
        let n = this.childRef.current.setCount(10, state => {
         console.log(state)   // 传入的cb回调函数就是当前的箭头函数,在子组件中的回调函数中执行的时候调用当前的函数,具体的 state值就是在组件中出入的值, 此片段中传入的就是  this.state  也就是子组件中的当前的 state 值 
     })
   console.log(n)
   }

     render() {
        return (
          <div>
            <h3>父组件    </h3>
            <button onClick={this.getChildInstance}>获取子组件</button>
            {/* 
          通过给自定义组件(子组件)绑定ref属性,来回去当前的组件实例
         通过实例对象,完成组件中的方法和属性的调用,实现父子间的传值
         绑定之后,父组件中就可以通过ref来获取子组件中的属性或者方法
         ref只能是在父组件中给调用的子组件绑定ref属性,也就是父中的子绑定,只此一种情况
            */}
            <Child ref={this.childRef} />
          </div>
       );
   }
}

export default App;

兄弟组件传值[状态提升]

贼简单,就是把兄弟间要用的数据在父组件中定义,方法也在父组件中定义,给一个子传值,给另一个传方法,或者是按需求个传个的

简单案例
import React, { Component } from 'react';
//  兄弟组件之间的传值,将兄弟共用信息提升到父组件中,

class Child1 extends Component {
  render() {
    // 父子间传值,自接受父的传值  this.props
    const  {count} = this.props
    return (
      <div>
        <h3>Child1 --- {count}</h3>
      </div>
    );
  }
}
class Child2 extends Component {
  render() {
    return (
      <div>
        <h3>Child2</h3>
        {/*  
          第一种写法无法给接收到的方法中传入参数
          一般都用第二种,可以传参,也可以不穿,看需求
        */}
        {/*  */}
        <button onClick={() => this.props.setCount(10)}>++修改值++</button>
      </div>
    );
  }
}
class App extends Component {

  state={
    count:100
  }

  setCount = n =>{
    this.setState(state =>({
      count : state.count + n 
    }))
  }
  render() {
    const {count} = this.state
    return (
      <div>
        <h3>父组件</h3>
        <hr />
        {/* 
          兄弟组件,通信可以将公用的数据提升到父组件中,实现共享
        */}
        <Child1 count={count} />
        <Child2 setCount={this.setCount}/>     
      </div>
    );
  }
}

export default App;

跨组件通信

  • 实现组件的跨层级通信 , 生产和消费
  • 在祖先节点中生产数据,data , 写在context对象当中,
  • 在要通信的子孙节点中通过context对象获取数据 , 值 方法
  • 必须要注意一点, 要先有数据才能消费数据,先生产在获取 , 且一个项目中可以有多个 context 对象
// 创建 context对象
import { createContext } from "react";
const ctx = createContext() 
//  provider 是将组件爱你中的state数据提供到子孙组件中去, 生产或者发布的过程
//  consumer 消费
export const {Consumer , Provider} = ctx
export default ctx  

 //在组件中调用   index文件
import React, { Component } from 'react';
//  ctx 是export default 默认导出的, { 解构是按需导出的 } , 默认导出的一定要写在按需导入的前面
import ctx, { Consumer, Provider } from "./context/appContext"

class Child1 extends Component { 
  render() {
    return (
      <div>
        <h3>Child1</h3>
        {/* 
            子孙组件在接收 祖先组件 context 传下来的值时 用consumer标签包裹 data 
        */}
        <Consumer>
          {data =>{
            return <h3> Child1组件 ---- count值: {data.count}</h3>
          }}
        </Consumer>
      </div>
    );
  }
}

//  推荐的消费方案
class Child2 extends Component {
  //  在类中的静态属性contextType赋值了context对象后,他就可以在后续的成员属性中用 this.context得到context对象
  //  简单说就是子组件可以通过设置静态的属性  contextType = ctx  让子孙组件可以获得祖先组件中的 context 对象,并使用对象中的值和方法 , 来修改祖先中的值,
  static contextType = ctx

  addCount = () => {
    console.log(this.context.setCount);
    this.context.setCount(10);
  }
  render() {
    return (
      <div>
        <button onClick={this.addCount}>++</button>
      </div>
    );
  }
} class App extends Component {

  //  祖先state数据,要给context对象来向下传递然后去消费
  state = {
    setCount:(n) => this.setState(state => ({count: state.count + n })),
    count: 100
  }

  //  为了方便传值,也为了代码的优雅,可以将值和方法,全部都写在state中
  // setCount = (n=1) =>{
  //   this.setState(state=>({
  //     count: state.count + n
  //   }))
  // }
  render() {
    return (
      <div>
        <h3>app</h3>
        <hr />
        {/*  
              向下传值的时候用provider标签包裹 , 子组件,直接在provider中传入向下传递的值
              和父子传值一样,无论是向后代组件传值还是方法,都是将从祖宗组件向下传值
        */}

        {/*  */}
        {/*  */}
        <Provider value={{...this.state , setCount: this.setCount}}>
          <Child1 />
          <hr />
          <Child2 />
        </Provider>
      </div>
    );
  }
}

export default App;

高阶组件(类组件用的多,函数用别的)

高阶组件的基础应用及相关的问题

本质上就是一个函数,传给他一个组件,返回一个新的组件,相当于给手机加了一个手机壳

扩展: js中高阶函数==》 1,把函数当做形参 2,返回一个函数

  • 实现步骤:
  • 创建一个函数, 指定函数的参数,参数应该 应大写字母开头,参数中要穿入一个组件
  • 在函数内部创建一个类组件,或者函数组件
  • 常见的作用:
  • 进行权限控制
  • 访问统计
  • 统一布局
  • 缺点: 增加了组件的层级,影响性能 , 并且还会影响Props的传值
//  配置高阶组件函数 文件    src/hoc/withLoad.js
import React , {Component , Fragment} from "react"
//  Fragment 是一个内置组件,它可以当作组件元素的顶层元素,但是他不会解析成html元素,就相当于只是一个占位文件
//  fragment使用频率很高,,react提供了加血方法: <>  且 简写无需导包

// 高阶他就是一个函数, 把组件当做参数传过来,对传入的组件进行增强后  返回
//  Cmp就是接收到的组件,
const withLayout = Cmp =>{

    console.log(new Cmp())

    //  返回一个类组件
    // return class Layout extends Component {
    return class  extends Component {   // 返回值的类直接就用在了组件中, 所以直接返回一个匿名的类即可
        render (){
            return (
                // 
// <> {/* 采用fragment后,相当于直接将fragment标签中的内容写在了引入的父组件的div中,也就是少了原本的 div标签*/} <h3>公用的组件头部</h3> <hr /> {/* 用高阶组件包裹组件 */} <Cmp /> <hr /> <h3>公共组件的尾部</h3> </> // //
) } } } export default withLayout // 被高阶组件调用的组件 , 通常是摸个页面的部分,调用后给此组件添加了一个新的布局 import React, { Component } from 'react'; // react 没有配置路径别名,只能写相对地址 import withLayout from 'src/hoc/withLayout'; class Home extends Component { displayName='home' render() { return ( <div className='home'> <h3>首页面</h3> </div> ); } } // 使用高阶组件来增强组件时 , 导出时要用高阶组件将导出的类包裹, 因为高阶组件是一个函数,返回的是一个对组件增强后的类, 所以在app祖宗组件中导入的也就是高阶组件返回的 包裹后的 深层组件 // 缺点: 改变了原组件的层级,加深了 export default withLayout(Home);
  • 如何解决高阶组件中的Props丢失问题:
  • 实际上就是在高阶组件的 方法中加一步, 先对祖先组件传过来的Props值深复制出来,对值进行相应的修改,然后将修改后的值 ,在通过接受的组件参数,传给组件,简单说高阶组件函数,相当成了数据的处理和转送中间层 修改值的时候不能直接对Props修改,要采用深复制的方法,克隆数据对克隆数据进行修改,并将克隆后的数据传递出去
  • 在子组件中,正常接收数据this.props,但要明白此时的数据时处理过后的高阶数据,不是元数据
 高阶组件的 类组件的封装方法
const withLayout = Cmp => {
    return class extends Component {
        render() {
            const arts = cloneDeep(this.props)
            arts.phone = arts.phone.slice(0, 3) + "****" + arts.phone.slice(7)
            return (
                <>
                    <h3>我是公共头部</h3>
                    <hr />
                    {/*  */}
                    {/*  推荐用法 */}
                    <Cmp {...arts} />
                    <hr />
                    <h3>我是公共低部</h3>
                </>
            )
        }
    }
}

 函数组件的写法
const withLayout = Cmp => props =>{
    //  在高阶组件函数中对数据进行处理  , 注意修改数据时,不能修改Props中的数据
    //  第一种处理方法 ,不常用
    // let phone = props.phone
    // phone = phone.slice(0,3) + "*****" + phone.slice(7)
    //  第二种, 使用深层克隆,将Props的数据当一份出来
    const arts = cloneDeep(props)
    arts.phone = arts.phone.slice(0,3) + "****" + arts.phone.slice(7)
    return (
        <>
            <h3>我是公用头部</h3>
            <hr />
            {/* 第一中处理方法,直接用修改后的值 */}
            {/*  */}

            {/* 当高阶组件对数据不进行处理时,则直接用展开语法,获取 各项数据即可 */}
            {/*     */}

            {/* 第二种处理方法 */}
            <Cmp {...arts}/>
            <hr  />
            <h3>我是公用底部</h3>
        </>
    )
}


子组件使用时
class Home extends Component {
    displayName='home'
    // console.log()  在类组件中,一切的执行语句都要写在,render中
    render() {
        //  在使用了高阶组件之后,Props的数据会丢失,不能在直接使用
        /* 
    要想获取数据,要先在高阶组件中进行数据出来 , 并且在高阶组件中修改后的值,手动的将修改后的值传入到 调用的组件中,也就是  Cmp中, 接着在子组件中,this.props 接受的值可以理解为是 高阶组件传过来的修改后的值,不是父组件直接传过来的值
        */
        console.log("phone" , this.props)
        const {phone} = this.props
        return (
            <div className='home'>
                <h3>首页面 --- {phone}</h3>
            </div>
        );
    }
}

高阶组件的反向继承 === 可以理解为对子组件重写的方案

  • 一般前端很少用到,但控制翻转能用到
  • 继承: 先执行父,在执行子 反向继承: 先执行子,在执行父
  • 作用: 用来渲染劫持 和 操作 state数据

用我自己的理解简单总结一下: 在app祖宗组件中,接受传入的子组件,这个子组件是经过高阶组件增强的,所谓增强就是原来的比较纯净的子组件,通过高阶组件增添了,属性、方法,设置app组件传过来的值,注意区别上面的解决Props传值丢失问题 , 此时的高阶组件在返回类时,继承的是接收到的子组件类型,可以理解为返回的值就是子组件类的扩展类,或者说增强版的子组件,app接受的就是这个增强版(改造后的)子组件 , 示例代码如下

子组件,就像个模版花瓶,任高阶组件调教
import React , { Component } from "react"
import withBtn  from "../../hoc/withBtn"
 
class Btn extends Component{
    render(){
        console.log(this.props)
        return <button></button>
    }
}
export default withBtn(Btn)  // 导出的就是调教好的

app祖宗组件,给高阶组件提供调教的值,方法
class App extends Component {
  clickHandler = () => {
    console.log(1111222333)
  }
  render() {
    return (
      <div>
         // title onClick 都是要穿的值
        <Btn title="打印" onClick={
          this.clickHandler
        }/>
      </div>
    );
  }
}

高阶组件对子组件进行具体的  增强
const withBtn = Cmp => {
// 这里继承的不是component 而是继承的 需要增强的类型,继承此类型以后,可以获得祖宗节点的穿的值
    return class extends Cmp {
        render() {
            const ele = React.cloneElement(super.render(), {
                onClick:this.props.onClick
            },
            this.props.title)
            return (
                <>
                // app 组件渲染的组件就是这个ele
                    {ele}
                </>
            )
        }
    }
}

memoization (计算属性 - 记忆组件)

  • 两次的计算结果相同返回结果,结算的结果不一样就返回新的结果
  • 安装: npm i -S memoize-one
import React, { Component } from 'react';
// 缓存组件 -- 计算属性
//  如果下一次的计算的依赖性没有发生改变,就会用上一次的结果缓存
//  是一个性能优化函数,可以起到一定的性能提升,也可以不用
import memoizeOne from 'memoize-one';

class App extends Component {

  state = {
    n1: 1,
    n2: 2
  }

  sum = (n1, n2) => {
    console.log("sum")
    return n1 + n2
  }

  //  高阶函数:  他就是一个高阶函数,形参是一个函数
  total = memoizeOne((n1, n2) => {
    console.log('total')
    return n2 + n1
  })
  render() {
    const { n1, n2 } = this.state
    return (
      <div>
        <span>{n1}</span>
        <span>+</span>
        <span>{n2}</span>
        <span>=</span>
   {/* this.sum 执行了三次,打印了三个sum, total执行了一次,因为三次的结果都相同 */}
        <span>{this.sum(n1, n2)}</span>
        <span>{this.sum(n1, n2)}</span>
        <span>{this.sum(n1, n2)}</span>
        <h3>{this.total(n1,n2)}</h3>
        <h3>{this.total(n1,n2)}</h3>
        <h3>{this.total(n1,n2)}</h3>
      </div>
    );
  }
}

export default App;

portals

portals提供的是将子组件渲染到父组件以外的Dom的方案

  • 在APP组件中的调用方法是一样的,但如果想让子组件挂在到别的Dom节点中,需要改变写法
基础了解,应用时 ,多数是挂在到动态生成的节点中去 ,下面是死的
import React, { Component } from 'react';
//  一个让子组件挂在到指定的元素节点中方法
import { createPortal } from 'react-dom';

class Child extends Component {
  render() {
    //  常规应用,挂在到的是root节点
    // return (
    //   <>
    //     

弹出层

// // ); return createPortal( <> <h3>弹出层</h3> </>, // 声明要挂载到的节点 document.getElementById('dialog') ) } } class App extends Component { render() { return ( <div> <h3>app</h3> <hr /> <Child /> </div> ); } } export default App;

实际中的应用

import React, { Component } from 'react';
//  创建一个让组件挂在到指定的元素节点中
import { createPortal } from 'react-dom';

class Child extends Component {
  //  生成弹窗的挂载节点
  constructor(props){
    super(props)
    //  在创建类实例的时候,将节点和方法都结构到对应的元素中
    const [el , remove] = this.createContainer()
  }
  
  //  生成挂载节点,返回的是挂载节点和删除节点的方法
  createContainer(){
    this.container = document.createElement('div')
    document.body.appendChild(this.container)
    return [()=> this.container.remove() , this.container]
  }

  //  如果要关闭弹窗直接注销生成的动态挂载节点即可, 直接调用 remove 方法 
  onClose = ()=>{
    this.remove()
  }
  render() {
    return createPortal(
      <>
      <h3>弹出层</h3>
      <button onClick={this.onClose}>关闭</button>
      </>,
      //  声明要挂载到的节点
    // document.getElementById('dialog')
    this.container

    )
  }
}
class App extends Component {
  state= {
    visable : false,
    dt: 1
  }
  render() {
    return (
      <div>
        <h3>app</h3>
        <button onClick={()=>{
          this.setState({
            visable:true ,
            dt: Date.now()   // 为了点击时 ,让代码发生变化,否则就不会触发二次显示
          })
        }}>显示</button>
        <hr />
        {this.state.visable ? (
          <Child key={this.state.dt}/>
        ): (<></>)
        }
      </div>
    );
  }
}

export default App;

装饰器

  • 用来包装或者扩展代码的功能,装饰器以: @ 符号开头的特属于法

  • 只能用在面向对象中,且需要用过bable编译才能在浏览器正常的运行, 函数中不用

  • 目前还在提案阶段,react没有内置方法,需要在webpack配置中进行修改

  • 两种方法:

    • 1, npm run eject 在react工程化中暴露出webpack配置, 但是次日方案不可逆,出错就寄了,不推荐

    • 2:

       npm i -D @craco/craco@7    修改或覆盖webpack配置
      
       在根目录中创建  craco.config.js 文件,对webpack进行配置 此文件用 commonjs的规范,对已有的webpack进行修改和增量配置 , 修改后重启应用
       
       npm i -D @babel/plugin-proposal-decorators@7  
      

类装饰器 , 类成员装饰器

类装饰器
target就是当前装饰的类  person
const decator =Target => {
  console.log(Target)
  //  扩展静态方法和属性
  Target.staticMsg = '我是一个静态属性'
  //  扩展动态属性和方法  这是添加给此类创建的实例化对象的,当前类本身不能使用此属性
  Target.prototype.msg = "成员属性"
}
装饰器的语法形式为@decator,其中decator是一个函数,接收类或类方法作为参数,并返回一个新的被修改后的类或方法。装饰器可以在类的声明之前或类方法的定义之前使用。
 @decator
class Person {}

类成员装饰器
//  target 当前的装饰类的实例对象
//  key装饰的成员名称
//  description 对于成员的信息的描述,是一个对象
status已被弃用
const readonly  = status= (target, key , description ) => {
  console.log(target, key , description)
  description.writable = !status
}

class Person {
  @readonly(true)
  title="你好"
}

const p = new Person()
p.title = "adv"
console.log(p.title)

装饰器方法调用高阶组件

与传统的高阶组件的调用方法基本一样的,就改了两个地方 装饰器的引入@高阶函数, 导出时正常导出,无须方法包裹 , 仅此两处

在定义好 高阶组件的函数后,在子组件中直接采用装饰器引入即可
import React, { Component } from 'react';
import withLayout from 'src/hoc/withLayout';

const fun = () => {
    console.log("第二个装饰器")
}
// 装饰器可以有多个,执行顺序为: 水平方向: 从右向左,垂直方向:从下向上
@withLayout  @fun
class Loading extends Component {
    render() {
        const { child } = this.props
        return (
            <div>
                {child ?? <h3>加载中。。。</h3>}
            </div>
        );
    }
}

export default Loading;

react项目支持配置

  • 修改或覆盖webpack配置
  • 在react工程中,暴露出webpack配置,npm run eject ,缺点是他不可逆, 暴露后就不能退回,
  • npm i -D @craco/craco@7 ==》 进行或覆盖webpack的配置
  • npm i -D @babel/plugin-proposal-decorators@7 bable配置

样式

  • react 不能像vue那样将排版,逻辑,样式全都写在一个文件中,所以样式需要单独解决
  • 写样式时需要注意的问题: 类名重复时会发生样式的覆盖,特别是祖孙组件之间
  • 解决方法有四种(样式隔离的方法):
  • 按照命名规范起样式别名: 模块名-样式名… 这是一定可以解决问题的方法
  • cssModel 方案, 把css解析成js文件,但css文件命名有要求: XXX.module.css/scss ,在组件中引用时: import style(自己起名) from “文件地址”className={style.具体的类名}
  • cssinjs(热门方案) 在js中写css,需要引入第三方的库(styled-components), 只需要定义 一个组件名,并指定其解析后的类型,然后用模版字符串定义他的样式,最后按需导入接口,用法和组件的用法一样,双标签包裹后定义内容
  • 用sass进行嵌套隔离,但是在最外面,名称要用 模块名-样式名 , 保证不重名

模版字符串扩展的 函数新语法

// 模范字符使用方法
function fn(...args){
    console.log(args)
}
fn(`asdafgasdfwe`)  // [asdfasdf]

let num = 100
let name = "嘿嘿"
fn(`asdlf${num}jals${name}d`)  // [ ['asdlf' , "jals" , "d"]  , 100 , "嘿嘿"]
// ${} 将数组拆分开,但仍为一个数组内的内容,添加的变量,单独为一个元素


//  剩余参数法  strs 接收第一个参数,其他的将以数组形式传入
这就是定义的第三种cssinjs方法
function fn2(strs , ...args){
    console.log(strs , args)
    let out = ''
    strs.forEach((item, index) =>{
        out += item + (args[index] ?? '')
    })
    console.log(out)
    const style = document.createElement('style')
    style.innerHTML= out
    document.head.appendChild(style)
}

// fn2(1,[2,3,4])  // 1,[2,3,4]
区别 fn2(`aa${num}aaa`)  和  fn2`aa${num}aaa`  两者不同
// 在使用模版字符串传值的时候,加小括号,就相当于整个模版字符串的值就是第一个参数 , 这个过程是,模版字符串先解析模版中的占位符中的动态变量,将他的值传进来,然后和静态部分组成一个新的字符串,作为参数传入方法中,作为第一个参数(字符串参数)
// !!  fn2(`asdlf${num}jals${name}d`) 

//  下面是js新语法: 若要提供自定义函数,需在模板字面量之前加上函数名(结果被称为带标签的模板)。此时,模板字面量被传递给你的标签函数,然后就可以在那里对模板文本的不同部分执行任何操作
// strs得到的是模版字符串的静态部分,args得到的是动态部分并将他们放在一个数组中
// fn2`asdlf${num}jals${name}d`
fn2`
.three{
color: purple;
font-size:20px;
background-color: green
}
`

四种方法详解 (样式就是正常的样式书写)

app 祖宗组件
import React, { Component } from 'react';
import Myhead from "./components/Myheader/index"
// 一
import "./app.css"
// 二
import style from "./style.module.css"

//  方法三  手撸法,很鸡肋,没有样式隔离,依旧有重名时的样式覆盖问题
function appStyle(strs , ...args) {
  let cs = ''
  strs.forEach((item,index) =>{
    cs += item +  (args[index] ?? "")
  })
  const style = document.createElement("style")
  style.innerHTML= cs
  document.head.appendChild(style)
}

appStyle`
.three{
  background-color:pink
}
`
class App extends Component {
  render() {
    return (
      <div>  
      {/* 失败版本,命名重复,只有app的样式生效 */}
        <h3 className='title'>app</h3>
        {/* 方法一 , 命名规范法  */}
        <h3 className='app-title'>app方法一</h3>
        {/* 方法二: cssmodule解析成js */}
        <h3 className={style.title}>app二</h3>
        {/*  方法三(手撸版): cssinjs 没有引入一个styled-compoents库时的用法,然后用模版字符串解析定义好的样式模版字符串 , 需要注意的是此方法和白给失败方法没有区别 , 依旧有重复类名样式就覆盖的问题,且子组件的权重最高,因为子组件后加载可能  */}
        <h3 className='three'>app三</h3>
        <hr />
          <Myhead />
      </div>
    );
  }
}
export default App;


// 子组件
import React, { Component } from 'react';
// 第一种:按命名规范命名
import  "./style.css"
//  用第二种方法,将css解析成js
import style from "./style.module.css"
// 第三种方法  
// styled-components 他 是实现了cssinjs的技术库

//  在没有引入技术库时的用法
//  剩余参数法  strs 接收第一个参数,其他的将以数组形式传入
function fn2(strs , ...args){
    console.log(strs , args)
    let out = ''
    strs.forEach((item, index) =>{
        out += item + (args[index] ?? '')
    })
    console.log(out)
    const style = document.createElement('style')
    style.innerHTML= out
    document.head.appendChild(style)
}

fn2`
.three{
color: purple;
font-size:20px;
background-color: green
}
`
class Head extends Component {
    render() {
        return (
            <div>
                {/* 会发生样式重叠的写法, 组件的class名相重复,样式只会有最后一个会生效 */}
               <h3 className='title'>我是头部失败版</h3>  
                {/* 第一种方案: 起不重复的名字   */}
               <h3 className='min-title'>我是头部一</h3>  
                {/* 第二种方法 */}
               <h3 className={style.title}>我是头部二</h3> 
               {/*  方法三: cssinjs方法用到es6中的模版字符串 */}
               <h3 className='three'>我是头三</h3>
            </div>
        );
    }
}

export default Head;

styled-components(cssinjs方法)

安装技术库 npm i -S styled-components

//  cssinjs 方法: 把css写在js中
// style-component库来实现
//  好处: 把样式当做组件来使用,定义的样式有隔离性
import  styled from "styled-components"

//  用styled-components创建一个组件名为Div(这个随便起) ,在编译后的HTML元素名称为div, 且随机生成一个类名,保证类名不重复
//  此Div中的样式为字符串模版中的样式、样式的写法和scss一样
//  要想样式有提示: 安装插件: vscode-styled-componets 即可
 export const  H3 = styled.h3`
   color : red;
   font-size: 30px;
`  // 看清楚 ,这是模版字符串
 
 
 import React, { Component } from 'react';
// 第三种方法, 引入技术库后 , 样式有很多,按需导入即可
import { H3 }  from "./style"
 
class Head extends Component {
    render() {
        return (
            <div>
               {/* 方法三: 最终版 , 将样式当组件来用 */}
               <H3>我是头三终极版</H3>
            </div>
        );
    }
}

export default Head;

cssinjs的样式嵌套 , 样式继承 ,接收props传值

因为cssinjs中定义样式是当成子组件再用,所以可以接受父组件的传值

import styled from "styled-components" 
export const  Div = styled.div`
   color : red;
   font-size: 30px;
   h3{
    color: pink;
      background-color: plum;
	& 表示当前的单个元素
      &:hover{
        color: black;
      }
   }
   div{
    width: 200px;
    height: 200px;
    background-color: blue;
   }
`
// 接收Props传值
export const ContentBtn = styled.button`
  font-size: 20px;
  height: 50px;
  line-height: 50px;
  border: 1px solid red;
  border-radius: 5px;
  outline: none;
如果所设置的样式带单位,就将单位写在{}外 ,如下
   color:${props => props.color ?? "red"}px
`
// 样式继承  
// 很简单就将之前定义好的组件名写在小括号里即可,会继承编译后的HTML元素和样式
export const Btn = styled(ContentBtn)`
  font-size: 14px;
`

//使用:
import {Div , Btn , ContentBtn} from "地址"
 <Div>
   <h3>我是头三终极版</h3>
   <div>世纪东方卡拉胶猎杀对决发啦手机打理发卡</div>
</Div>
<Btn>按钮</Btn>
 {/* 传值 */}
<ContentBtn color="red">按钮</ContentBtn>

第四种解决css隔离的方法: scss样式,这是最重要的方法

创建scss文件,在安装scss处理器, npm i -D sass , 引入文件即可

sass分为 dart-sass[主流解析器,npm下载即可] / node-sass解析器,现在不好用要梯子

子组件的样式引用  components文件中的组件
import React, { Component } from 'react';
import "./style.scss"
import style from "./scss.module.scss"

class Scss extends Component {
    render() {
        return (
            <div>
                {/*  还是要注意重名问题,组件中元素的calss名要沟通好别重复 */}
                <div className='my-header'>
                    <div className='title'>我是一个标题</div>
                </div>

                {/*  也可以使用 XXX.module.scss  */}
                {/*  但是要注意两点,使用是要用 className={引入名.XXX}  有-的非正常属性名要用函数签名 , {style['XX-XX']} */}
                <div className={style['my-headd']}>
                    <div className={style.title}>我是第二个标题</div>
                </div>
            </div>
        );
    }
}

export default Scss;

//app组件正常引入即可
import React, { Component } from 'react';
import Myhead from "./components/Myheader/scss"

class App extends Component {
  render() {
    return (
      <div>
        <Myhead />
      </div>
    );
  }
}
export default App;

export const ContentBtn = styled.button`
  font-size: 20px;
  height: 50px;
  line-height: 50px;
  border: 1px solid red;
  border-radius: 5px;
  outline: none;
//如果所设置的样式带单位,就将单位写在{}外 ,如下
   color:${props => props.color ?? "red"}px
`
// 样式继承  
// 很简单就将之前定义好的组件名写在小括号里即可,会继承编译后的HTML元素和样式
export const Btn = styled(ContentBtn)`
  font-size: 14px;
`

//使用:
import {Div , Btn , ContentBtn} from "地址"
 <Div>
   <h3>我是头三终极版</h3>
   <div>世纪东方卡拉胶猎杀对决发啦手机打理发卡</div>
</Div>
<Btn>按钮</Btn>
 {/* 传值 */}
<ContentBtn color="red">按钮</ContentBtn>

第四种解决css隔离的方法: scss样式,这是最重要的方法

创建scss文件,在安装scss处理器, npm i -D sass , 引入文件即可

sass分为 dart-sass[主流解析器,npm下载即可] / node-sass解析器,现在不好用要梯子

子组件的样式引用  components文件中的组件
import React, { Component } from 'react';
import "./style.scss"
import style from "./scss.module.scss"

class Scss extends Component {
    render() {
        return (
            <div>
                {/*  还是要注意重名问题,组件中元素的calss名要沟通好别重复 */}
                <div className='my-header'>
                    <div className='title'>我是一个标题</div>
                </div>

                {/*  也可以使用 XXX.module.scss  */}
                {/*  但是要注意两点,使用是要用 className={引入名.XXX}  有-的非正常属性名要用函数签名 , {style['XX-XX']} */}
                <div className={style['my-headd']}>
                    <div className={style.title}>我是第二个标题</div>
                </div>
            </div>
        );
    }
}

export default Scss;

//app组件正常引入即可
import React, { Component } from 'react';
import Myhead from "./components/Myheader/scss"

class App extends Component {
  render() {
    return (
      <div>
        <Myhead />
      </div>
    );
  }
}

export default App;


你可能感兴趣的:(react.js,前端,前端框架)