一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

五、react脚手架

1、前端脚手架
* 对于现在比较流行的三大框架都有属于自己的脚手架
    - vue的脚手架:vue-cli
    - angular的脚手架:angular-cli
    - react的脚手架:create-react-app
* 它们的作用都是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好。
    - 目前这些脚手架都是使用node编写的,并且都是基于webpack的
    - 所以我们必须在自己的电脑上安装node环境
* 安装node
    - 官网地址:https://nodejs.org/en/download/
    - 注意:这里推荐大家下载LTS(long-term support)版本,是长期支持版本,会比较稳定
    - 安装过程中,会自动配置环境变量
    - 安装时,会同时帮助我们安装npm管理工具
* 包管理工具
    - 全称node package manager,即“node包管理器”
    - 另外,还有一个大名鼎鼎的node包管理工具yarn。yarn是为了弥补npm的一些缺陷而出现的。早期的npm
      存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列问题。虽然从npm5版本开始,进行了很多
      的升级和改进,但是依然很多人喜欢使用yarn
    - react脚手架默认也是使用yarn:npm install -g yarn
2、yarn和npm命令对比
npm yarn
npm install yarn install
npm install [package] yarn add [package]
npm install --save [package] yarn add [package]
npm install --save-dev [package] yarn add [package] [--dev/-D]
npm rebuild yarn install --force
npm uninstall [package] yarn remove [package]
npm uninstall --save [package] yarn remove [package]
npm uninstall --save-dev [package] yarn remove [package]
npm uninstall --save-optional [package] yarn remove [package]
npm cache clean yarn cache clean
rm -rf node_modules && npm install yarn upgrade
3、安装脚手架
* cnpm安装(了解)
    - npm install -g cnpm --registry=https://registry.npm.taobao.org
* 安装react脚手架
    - npm install -g create-react-app
* 创建react项目(注意:项目名称不能包含大写字母)
    - create-react-app 项目名称
    - npm run start
* 项目结构
    - public/manifest.json:web app配置相关
    - src/serviceWorker.js:注册pwa相关的代码(注意:需要被调用)
    - public/robots.txt:爬虫协议
    - src/reportWebVitals.js:谷歌定义的web性能指标
        ~ LCP:最大内容渲染时间
        ~ FID:首次输入延迟
        ~ CLS:累计布局偏移
        ~ FCP:首次内容绘制
        ~ TTFB:首字节到达的时间点
* 了解pwa
    - 全称progressive web app,即渐进式web应用
    - 添加上app manifest和service worker来实现pwa的安装和离线等功能
    - 可以将一些移动端页面添加至桌面
    - 允许一系列类似于native app相关的功能,比如实现了消息推送
* webpack是什么
    - 静态模块打包器(依赖关系图)
* react脚手架中的webpack
    - react脚手架将webpack相关的配置隐藏起来了
    - 如果我们希望看到webpack的配置信息,应该怎么来做呢
        ~ 执行package.json中的一个脚本:{"scripts": {"eject": "react-scripts eject"}}
4、react脚手架项目基础代码
  • src/index.js
// 使用jsx语法时会使用到(jsx的本质)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);
  • src/App.js
import React from "react";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      counter: 0
    }
  }

  render() {
    return (<div>
      <div>{this.state.counter}</div>
      <button onClick={this.increment.bind(this)}>+1</button>
      <button onClick={this.decrement.bind(this)}>-1</button>
    </div>)
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  decrement() {
    this.setState({
      counter: this.state.counter - 1
    })
  }
}

export default App;

六、react组件化开发

1、组件化开发
* 分而治之的思想
* 什么是组件化开发呢?
    - 我们将一个完整的页面分成很多个组件,每个组件都用于实现页面的一个功能块,而每一个组件又可以细分,
      而组件本身又可以在多个地方复用
* react的组件化
    - 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
    - 任何的应用都会被抽象成一棵组件树
* react的组件相对于vue更加的灵活和多样,按照不同的方式可以分成很多类组件
    - 根据组件的定义方式,可以分为:函数组件(function component)和类组件(class component)
    - 根据组件内部是否有状态需要维护,可以分成:无状态组件(stateless component)和有状态组件(stateful component)
    - 根据组件的不同职责,可以分成:展示型组件(presentational component)和容器型组件(container component)
* 这些概念有很多重叠,但是他们最主要是关注数据逻辑和ui展示的分离
    - 函数组件、无状态组件、展示型组件主要关注ui的展示
    - 类组件、有状态组件、容器型组件主要关注数据逻辑
2、类组件
import React, {Component} from "react";

/**
 * 一、类组件的定义有如下要求
 *     - 组件的名称是大写字符开头(无论类组件还是函数组件)
 *     - 类组件需要继承自React.Component
 *     - 类组件必须实现render函数
 *     - 标签名称不能大写(原生html不区分大小写,而jsx区分大小写)
 */
class App extends Component {
  constructor() {
    super();
    this.state = {
      counter: 0
    }
  }

  render() {
    return (<div>
      <div>{this.state.counter}</div>
      <button onClick={this.increment.bind(this)}>+1</button>
      <button onClick={this.decrement.bind(this)}>-1</button>
    </div>)
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  decrement() {
    this.setState({
      counter: this.state.counter - 1
    })
  }
}

export default App;
3、函数式组件
import React from "react";

/**
 * 一、函数式组件的特点
 *     - 没有this对象
 *     - 没有内部的状态(hooks)
 */
export default function App() {
  // 2、render函数的返回值
  // 2.1、react元素
  return (<div>黄婷婷</div>)

  // 2.2、数组或fragments
  // return ([<div>姜贞羽</div>, <div>孟美岐</div>])

  // 2.3、portals

  // 2.4、字符串或数字类型(渲染成文本节点)
  // return "张婧仪"

  // 2.5、布尔类型或null(什么都不渲染)
  // return true
}
4、认识生命周期
* 生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段
    - 比如装载阶段(mount),组件第一次在dom树中被渲染的过程
    - 比如更新过程(update),组件状态发生变化,重新更新渲染的过程
    - 比如卸载过程(unmount),组件从dom树中被移除的过程
* react内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数
    - 比如实现componentDidMount函数:组件已经挂载到dom上时,就会回调
    - 比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调
    - 比如实现componentWillUnmount函数:组件即将被移除时,就会回调
    - 我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能
* 我们谈react生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的
  (后面我们可以通过hooks来模拟一些生命周期的回调)
5、常用生命周期
import React from "react";

class Cpn extends React.Component {
  render() {
    return (<h3>黄婷婷</h3>)
  }

  /**
   * 1、componentWillUnmount()会在组件卸载及销毁之前直接调用
   *     - 在此方法中执行必要的清理操作
   *     - 例如:清除timer,取消网络请求或清除在componentDidMount()中创建的订阅等
   */
  componentWillUnmount() {
    console.log("卸载")
  }
}

export default class App extends React.Component {
  /**
   * 1、如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数
   * 2、constructor中通常只做两件事情
   *     - 通过给this.state赋值对象来初始化内部的state
   *     - 为事件绑定实例(this)
   */
  constructor() {
    super();
    console.log("【挂载】之前会执行")
    this.state = {
      counter: 0,
      show: true
    }
  }

  render() {
    console.log("【挂载】或【更新】之前会执行")
    return (<div>
      <h3>当前计数{this.state.counter}</h3>
      <button onClick={e => this.increment()}>计数+1</button>
      <hr/>
      <button onClick={e => this.changeCpnShow()}>显示隐藏</button>
      {this.state.show && <Cpn/>}
    </div>)
  }

  /**
   * 1、componentDidMount()会在组件挂载后(插入dom树中)立即调用
   * 2、componentDidMount中通常进行哪里操作呢
   *     - 依赖于dom的操作可以在这里进行
   *     - 在此处发送网络请求就最好的地方(官方建议)
   *     - 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
   */
  componentDidMount() {
    console.log("挂载")
  }

  /**
   * 1、componentDidUpdate()会在更新后被立即调用,首次渲染不会执行此方法
   *     - 当组件更新后,可以在此处对dom进行操作
   *     - 如果你对更新前后的props进行了比较,也可以选择在此处进行网络请求
   *       (例如:当props未发生变化时,则不会执行网络请求)
   */
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("更新")
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  changeCpnShow() {
    this.setState({
      show: !this.state.show
    })
  }
}
6、不常用生命周期函数
* 除了上面介绍的生命周期之外,还有一些不常用的生命周期函数
    - getDerivedStateFromProps:state的值在任何时候都依赖于props时使用;
      该方法返回一个对象来更新state
    - getSnapshotBeforeUpdate:在React更新dom之前回调的一个函数,可以获取
      dom更新前的一些信息(比如说滚动位置);
    - shouldComponentUpdate:该生命周期函数很常用,但是我们等待讲性能优化时
      再来详细讲解
* 另外,React中还提供了一些过期的生命周期函数,这些函数已经不推荐使用
* 更详细的生命周期相关的内容,可以参考官网:
  https://zh-hans.reactjs.org/docs/react-component.html
7、组件的嵌套和组件间的通信
  • 概念
* 父组件在展示子组件,可能会传递一些数据给子组件
    - 父组件通过【属性=值】的形式来传递给子组件数据
    - 子组件通过【props】参数获取父组件传递过来的数据
* 父组件传递子组件-类组件和函数组件
  • 类组件父传子通信
import React from "react";

class Header extends React.Component {
  /*
  // 默认的构造函数(非显式调用构造函数),new对象时传入的参数,全部由super()接收
  constructor(props) {
    // super()不传入props,源码中实例也会通过属性赋值的方式添加上props
    super(props);
    // 父类中有被调用
    // this.props = props
  }*/

  render() {
    return (<h3>{this.props.name}</h3>)
  }
}

class Bodyer extends React.Component {
  render() {
    return (<div>
      <Lefter></Lefter>
      <Righter></Righter>
    </div>)
  }
}

class Lefter extends React.Component {
  render() {
    return (<h3>左边</h3>)
  }
}

class Righter extends React.Component {
  render() {
    return (<h3>右边</h3>)
  }
}

class Footer extends React.Component {
  render() {
    return (<h3>尾部</h3>)
  }
}

export default class App extends React.Component {
  render() {
    return (<div>
      <Header name="黄婷婷"></Header>
      <Bodyer></Bodyer>
      <Footer></Footer>
    </div>)
  }
}
  • 函数组件父传子通信,以及属性验证
import React from "react";
import PropTypes from "prop-types";

function Header(props) {
  return (<div>
    <h3>姓名:{props.name}</h3>
    <h3>年龄:{props.age}</h3>
    <h3>朋友:</h3>
    <ul>{props.friends.map(item => <li>{item}</li>)}</ul>
  </div>)
}

/*
// 类组件写法
class Header extends React.Component {
  static propTypes = {}
  static defaultProps = {}
}*/
Header.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  friends: PropTypes.array
}
Header.defaultProps = {
  age: 18
}

export default class App extends React.Component {
  render() {
    return (<div>
      <Header name="孟美岐" friends={['黄婷婷', '佟丽娅']}/>
    </div>)
  }
}
  • 子组件传递父组件:函数传递
import React from "react";

export class CounterButton extends React.Component {
  render() {
    const {onClick} = this.props

    return (<div>
      <button onClick={onClick}>+</button>
    </div>)
  }
}

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    }
  }

  render() {
    return (<div>
      <h2>当前计数:{this.state.counter}</h2>
      <CounterButton onClick={e => this.increment()}/>
    </div>)
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}
8、组件通信综合案例
  • src/layout/App.js
import React from "react";
import TabControl from "../components/TabControl";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      currentTitle: '新款'
    }
    // 通常不需要更改的数据,不在state中定义
    this.titles = ['新款', '精选', '流行']
  }

  render() {
    return (<div>
      <TabControl itemClick={index => this.itemClick(index)} titles={this.titles}/>
      <div>{this.state.currentTitle}</div>
    </div>)
  }

  itemClick(index) {
    this.setState({
      currentTitle: this.titles[index]
    })
  }
}
  • src/components/TabControl.js
import React from "react";
import PropTypes from "prop-types";
import "../styles/index.css"

export default class TabControl extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      currentIndex: 0
    }
  }

  static propTypes = {
    titles: PropTypes.array.isRequired
  }

  render() {
    return (<div className="tab-control">
      {this.props.titles.map((item, index) => {
        return (<div className={"tab-control__item" + (index === this.state.currentIndex ? ' active' : '')}
                     key={item} onClick={e => this.itemClick(index)}>{item}</div>)
      })}
    </div>)
  }

  itemClick(index) {
    this.setState({
      currentIndex: index
    })
    this.props.itemClick(index)
  }
}
  • src/styles/index.css
.tab-control {
  display: flex;
  text-align: center;
  line-height: 44px;
}

.tab-control__item {
  flex: 1;
}

.active {
  color: red;
}
9、React实现slot
  • src/layout/App.js
import React from "react";
import "../styles/index.css"

export default class App extends React.Component {
  render() {
    /**
     * <div class="layout"><h3>这是一段中文</h3></div>
     *     - div:元素
     *     - class:属性
     *     - h3:内容
     */
    return (<div>
      <NavBar className="navbar">
        <div className="navbar__left">&lt;</div>
        <div className="navbar__center">标题</div>
        <div className="navbar__right">...</div>
      </NavBar>
      <NavBar2 className="navbar"
               navbarLeft={<div className="navbar__left">&lt;</div>}
               navbarCenter={<div className="navbar__center">标题</div>}
               navbarRight={<div className="navbar__right">...</div>}/>
    </div>)
  }
}

class NavBar extends React.Component {
  render() {
    // 内容方式传入插槽
    return (<div className={this.props.className}>
      {this.props.children[0]}
      {this.props.children[1]}
      {this.props.children[2]}
    </div>)
  }
}

class NavBar2 extends React.Component {
  render() {
    // 属性方式传入插槽
    return (<div className={this.props.className}>
      {this.props.navbarLeft}
      {this.props.navbarCenter}
      {this.props.navbarRight}
    </div>)
  }
}
  • src/styles/index.css
body {
  margin: 0;
}

.navbar {
  display: flex;
  text-align: center;
  height: 44px;
  line-height: 44px;
}

.navbar__left, .navbar__right {
  width: 70px;
  background: red;
}

.navbar__center {
  flex: 1;
  background: green;
}
10、context应用场景-跨组件的通信
  • 通过props自上而下传递
import React from "react";

export class ProfileHeader extends React.Component {
  render() {
    return (<div>
      <div>{this.props.name}</div>
      <div>{this.props.age}</div>
    </div>)
  }
}

export class Profile extends React.Component {
  render() {
    return (<div>
      {/* 使用jsx特有的属性展开语法替代<ProfileHeader name={this.props.name} age={this.props.age}/> */}
      <ProfileHeader {...this.props}/>
    </div>)
  }
}

export default class App extends React.Component {
  render() {
    return (<Profile name="黄婷婷" age={18}/>)
  }
}
  • 通过context传递(类组件)
import React from "react";

// 可以设置默认值
const UserContext = React.createContext({
  name: "佟丽娅",
  age: 18
})

export class ProfileHeader extends React.Component {
  render() {
    return (<div>
      <div>{this.context.name}</div>
      <div>{this.context.age}</div>
    </div>)
  }

  static contextType = UserContext
}

export class Profile extends React.Component {
  render() {
    return (<ProfileHeader/>)
  }
}

export default class App extends React.Component {
  render() {
    // 通过value属性赋值
    return (<UserContext.Provider value={{name: '孟美岐', age: 19}}>
      <Profile/>
    </UserContext.Provider>)
  }
}
  • 通过context传递(函数组件)
import React from "react";

const UserContext = React.createContext({
  name: "佟丽娅",
  age: 18
})
const ThemeContext = React.createContext({
  color: "black"
})

function ProfileHeader() {
  // 多个context需要嵌套使用非常恶心
  return (<UserContext.Consumer>
    {value => {
      return (<ThemeContext.Consumer>
        {theme => {
          return (<div style={{color: theme.color}}>
            <div>{value.name}</div>
            <div>{value.age}</div>
          </div>)
        }}
      </ThemeContext.Consumer>)
    }}
  </UserContext.Consumer>)
}

export class Profile extends React.Component {
  render() {
    return (<ProfileHeader/>)
  }
}

export default class App extends React.Component {
  render() {
    return (<UserContext.Provider value={{name: '姜贞羽', age: 20}}>
      <ThemeContext.Provider value={{color: 'red'}}>
        <Profile/>
      </ThemeContext.Provider>
    </UserContext.Provider>)
  }
}
posted on 2022-09-30 11:16  一路繁花似锦绣前程  阅读(19)  评论(0编辑  收藏  举报