react学习路径

一份很好的前后端学习笔记:个人博客,react笔记是尚硅谷中的笔记 但不是很详细

React应用(基于React脚手架) (gitee.io)

一份很全的react学习路径

linjunc/react-study: 🎉 Here are all the high-quality React articles, a React repository worth learning (github.com)

里面的笔记也都是尚硅谷里的,这里整理一下用作日常复习

React 从入门到入土(一)– 基础知识以及 jsx 语法

先附上React官网 ,有很多问题都要通过查询官方文档来解决,要学会查文档~

一、React 简介

1. 关于 React

整几个面试题来认识一下~~

什么是 React ?

React 是一个用于构建用户界面的 JavaScript 库。

  • 是一个将数据渲染为 HTML 视图的开源 JS 库
  • 它遵循基于组件的方法,有助于构建可重用的 UI 组件
  • 它用于开发复杂的交互式的 web 和移动 UI

React 有什么特点?

  1. 使用虚拟 DOM 而不是真正的 DOM
  2. 它可以用服务器渲染
  3. 它遵循单向数据流或数据绑定
  4. 高效
  5. 声明式编码,组件化编码

React 的一些主要优点?

  1. 它提高了应用的性能
  2. 可以方便在客户端和服务器端使用
  3. 由于使用 JSX,代码的可读性更好
  4. 使用React,编写 UI 测试用例变得非常容易

2. Hello React

首先需要引入几个 react 包,我直接用的是老师下载好的

  • React 核心库、操作 DOM 的 react 扩展库、将 jsx 转为 js 的 babel 库

react1

const VDOM = <h1>Hello,React</h1>
ReactDOM.render(VDOM,document.querySelector(".test"))

3. 虚拟 DOM 和真实 DOM 的两种创建方法

3.1 JS 创建虚拟 DOM

//1.创建虚拟DOM,创建嵌套格式的dom
const VDOM=React.createElement('h1',&#123;id:'title'&#125;,React.createElement('span',&#123;&#125;,'hello,React'))
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.querySelector('.test'))

3.2 Jsx 创建虚拟DOM

//1.创建虚拟DOM
    const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
        <h1 id="title">
            <span>Hello,React</span>
        </h1>
    )
//2.渲染虚拟DOM到页面
    ReactDOM.render(VDOM,document.querySelector('.test'))

js 的写法并不是常用的,常用jsx来写,毕竟JSX更符合书写的习惯

二、jsx 语法

  1. 定义虚拟DOM,不能使用“”

  2. 标签中混入JS表达式的时候使用{}

id = &#123;myId.toUpperCase()&#125;
  1. 样式的类名指定不能使用class,使用className

  2. 内敛样式要使用{{}}包裹

style=&#123;&#123;color:'skyblue',fontSize:'24px'&#125;&#125;
  1. 不能有多个根标签,只能有一个根标签

  2. 标签必须闭合,自闭合也行

  3. 如果小写字母开头,就将标签转化为 html 同名元素,如果 html 中无该标签对应的元素,就报错;如果是大写字母开头,react 就去渲染对应的组件,如果没有就报错

记几个

1. 注释

写在花括号里

ReactDOM.render(
    <div>
    <h1>小丞</h1>
    &#123;/*注释...*/&#125;
     </div>,
    document.getElementById('example')
);

2. 数组

JSX 允许在模板中插入数组,数组自动展开全部成员

var arr = [
  <h1>小丞</h1>,
  <h2>同学</h2>,
];
ReactDOM.render(
  <div>&#123;arr&#125;</div>,
  document.getElementById('example')
);

tip: JSX 小练习

根据动态数据生成 li

const data = ['A','B','C']
const VDOM = (
    <div>
        <ul>
            &#123;
                data.map((item,index)=>&#123;
                    return <li key=&#123;index&#125;>&#123;item&#125;</li>
                &#125;)
            &#125;
        </ul>
    </div>
)
ReactDOM.render(VDOM,document.querySelector('.test'))

React 从入门到入土(二)– 面向组件编程

一、组件的使用

当应用是以多组件的方式实现,这个应用就是一个组件化的应用

注意:

  1. 组件名必须是首字母大写

  2. 虚拟DOM元素只能有一个根元素

  3. 虚拟DOM元素必须有结束标签 < />

渲染类组件标签的基本流程

  1. React 内部会创建组件实例对象

  2. 调用render()得到虚拟 DOM ,并解析为真实 DOM

  3. 插入到指定的页面元素内部

1. 函数式组件

//1.先创建函数,函数可以有参数,也可以没有,但是必须要有返回值 返回一个虚拟DOM
function Welcome(props) &#123;
  return <h1>Hello, &#123;props.name&#125;</h1>;
&#125;
//2.进行渲染
ReactDOM.Render(<Welcom name = "ljc" />,document.getElementById("div"));

上面的代码经历了以下几步

  1. 我们调用 ReactDOM.render() 函数,并传入 <Welcome name="ljc" /> 作为参数。
  2. React 调用 Welcome 组件,并将 {name: 'ljc'} 作为 props 传入。
  3. Welcome 组件将 Hello, ljc 元素作为返回值。
  4. React DOM 将 DOM 高效地更新为 Hello,ljc

2. 类式组件

weather

class MyComponent extends React.Component &#123;
    state = &#123;isHot:false&#125;
    render() &#123;
        const &#123;isHot&#125; = this.state
        return <h1 onClick=&#123;this.changeWeather&#125;>今天天气很&#123;isHot?'炎热':'凉爽'&#125;</h1>
    &#125;
    changeWeather = ()=>&#123;
        const isHot = this.state.isHot
        this.setState(&#123;isHot:!isHot&#125;)
    &#125;
&#125;
ReactDOM.render(<MyComponent/>,document.querySelector('.test'))

这玩意底层不简单,this的指向真的需要好好学习

在优化过程中遇到的问题

  1. 组件中的 render 方法中的 this 为组件实例对象
  2. 组件自定义方法中由于开启了严格模式,this 指向 undefined 如何解决
    1. 通过 bind 改变 this 指向
    2. 推荐采用箭头函数,箭头函数的 this 指向
  3. state 数据不能直接修改或者更新

3. 其他知识

包含表单元素的组件分为非受控租价与受控组件

  • 受控组件:表单组件的输入组件随着输入并将内容存储到状态中(随时更新)
  • 非受控组件:表单组件的输入组件的内容在有需求的时候才存储到状态中(即用即取)

二、组件实例三大属性

1. state

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。

简单的说就是组件的状态,也就是该组件所存储的数据

类式组件中的使用

image-20210720203721926

使用的时候通过this.state调用state里的值

在类式组件中定义state

  • 在构造器中初始化state
  • 在类中添加属性state来初始化

修改 state

类式组件的函数中,直接修改state

this.state.weather = '凉爽'

页面的渲染靠的是render函数

这时候会发现页面内容不会改变,原因是 React 中不建议 state不允许直接修改,而是通过类的原型对象上的方法 setState()

setState()

this.setState(partialState, [callback]);
  • partialState: 需要更新的状态的部分对象
  • callback: 更新完状态后的回调函数

有两种写法:写法1

this.setState(&#123;
    weather: "凉爽"
&#125;)

写法2:

// 传入一个函数,返回x需要修改成的对象,参数为当前的 state
this.setState(state => (&#123;count: state.count+1&#125;);

setState是一种合并操作,不是替换操作


  • 在执行 setState操作后,React 会自动调用一次 render()
  • render() 的执行次数是 1+n (1 为初始化时的自动调用,n 为状态更新的次数)

2. props

state不同,state是组件自身的状态,而props则是外部传入的数据

类式组件中使用

image-20210720211554914

在使用的时候可以通过 this.props来获取值 类式组件的 props:

  1. 通过在组件标签上传递值,在组件中就可以获取到所传递的值
  2. 在构造器里的props参数里可以获取到 props
  3. 可以分别设置 propTypesdefaultProps 两个属性来分别操作 props的规范和默认值,两者都是直接添加在类式组件的原型对象上的(所以需要添加 static
  4. 同时可以通过...运算符来简化

image-20210720212505232

函数式组件中的使用

函数在使用props的时候,是作为参数进行使用的(props)

image-20210720213304037

函数组件的 props定义:

  1. 在组件标签中传递 props的值
  2. 组件函数的参数为 props
  3. props的限制和默认值同样设置在原型对象上

3. refs

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

在我们正常的操作节点时,需要采用DOM API 来查找元素,但是这样违背了 React 的理念,因此有了refs

有三种操作refs的方法,分别为:

  • 字符串形式
  • 回调形式
  • createRef形式

字符串形式refs

image-20210720215332387

虽然这个方法废弃了,但是还能用,还很好用hhh~

回调形式的refs

组件实例的ref属性传递一个回调函数c => this.input1 = c (箭头函数简写),这样会在实例的属性中存储对DOM节点的引用,使用时可通过this.input1来使用

使用方法

<input ref=&#123;c => this.input1 = c &#125; type="text" placeholder="点击按钮提示数据"/>

我的理解

c会接收到当前节点作为参数,ref的值为函数的返回值,也就是this.input1 = c,因此是给实例下的input1赋值

createRef 形式(推荐写法)

React 给我们提供了一个相应的API,它会自动的将该 DOM 元素放入实例对象中

我们先给DOM元素添加ref属性

<input ref=&#123;this.MyRef&#125; type="text" placeholder="点击弹出" />
<input ref=&#123;this.MyRef1&#125; type="text" placeholder="点击弹出" />

通过API,创建React的容器,会将DOM元素赋值给实例对象的名称为容器的属性的current,好烦..

MyRef = React.createRef();
MyRef1 = React.createRef();

注意:专人专用,好烦,一个节点创建一个容器

//调用
btnOnClick = () =>&#123;
    //创建之后,将自身节点,传入current中
    console.log(this.MyRef.current.value);
&#125;

注意:我们不要过度的使用 ref,如果发生时间的元素刚好是需要操作的元素,就可以使用事件对象去替代。过度使用有什么问题我也不清楚,可能有 bug 吧

4. 事件处理

  1. React 使用的是自定义事件,而不是原生的 DOM 事件

  2. React 的事件是通过事件委托方式处理的(为了更加的高效)

  3. 可以通过事件的 event.target获取发生的 DOM 元素对象,可以尽量减少 refs的使用

image-20210720222147149

三、高阶函数

关于这部分的知识,之前的笔记有记过了,我真是太棒了

链接高阶函数,关于AOP,偏函数,柯里化都有不错的记录,感觉还是不错的

React 入门(三) – 生命周期 LifeCycle

引言

在 React 中为我们提供了一些生命周期钩子函数,让我们能在 React 执行的重要阶段,在钩子函数中做一些事情。那么在 React 的生命周期中,有哪些钩子函数呢,我们来总结一下

React 生命周期

React 生命周期主要包括三个阶段:初始化阶段,更新阶段,销毁阶段

初始化阶段

1. constructor 执行

constructor 在组件初始化的时候只会执行一次

通常它用于做这两件事

  1. 初始化函数内部 state
  2. 绑定函数
constructor(props) &#123;
    console.log('进入构造器');
    super(props)
    this.state = &#123; count: 0 &#125;
&#125;

现在我们通常不会使用 constructor 属性,而是改用类加箭头函数的方法,来替代 constructor

例如,我们可以这样初始化 state

state = &#123;
    count: 0
&#125;;

2. static getDerivedStateFromProps 执行 (新钩子)

这个是 React 新版本中新增的2个钩子之一,据说很少用。

getDerivedStateFromProps 在初始化和更新中都会被调用,并且在 render 方法之前调用,它返回一个对象用来更新 state

getDerivedStateFromProps 是类上直接绑定的静态(static)方法,它接收两个参数 propsstate

props 是即将要替代 state 的值,而 state 是当前未替代前的值

注意:state 的值在任何时候都取决于传入的 props ,不会再改变

如下

static getDerivedStateFromProps(props) &#123;
    return props
&#125;
ReactDOM.render(<Count count="109"/>,document.querySelector('.test'))

count 的值不会改变,一直是 109

2. componentWillMount 执行(即将废弃)

如果存在 getDerivedStateFromPropsgetSnapshotBeforeUpdate 就不会执行生命周期componentWillMount

该方法只在挂载的时候调用一次,表示组件将要被挂载,并且在 render 方法之前调用。

这个方法在 React 18版本中将要被废弃,官方解释是在 React 异步机制下,如果滥用这个钩子可能会有 Bug

3. render 执行

render() 方法是组件中必须实现的方法,用于渲染 DOM ,但是它不会真正的操作 DOM,它的作用是把需要的东西返回出去。

实现渲染 DOM 操作的是 ReactDOM.render()

注意:避免在 render 中使用 setState ,否则会死循环

4. componentDidMount 执行

componentDidMount 的执行意味着初始化挂载操作已经基本完成,它主要用于组件挂载完成后做某些操作

这个挂载完成指的是:组件插入 DOM tree

初始化阶段总结

执行顺序 constructor -> getDerivedStateFromProps 或者 componentWillMount -> render -> componentDidMount

image-20210821102153009

更新阶段

image-20210821102622645

这里记录新生命周期的流程

1. getDerivedStateFromProps 执行

执行生命周期getDerivedStateFromProps, 返回的值用于合并 state,生成新的state

2. shouldComponentUpdat 执行

shouldComponentUpdate() 在组件更新之前调用,可以通过返回值来控制组件是否更新,允许更新返回 true ,反之不更新

3. render 执行

在控制是否更新的函数中,如果返回 true 才会执行 render ,得到最新的 React element

4. getSnapshotBeforeUpdate 执行

在最近一次的渲染输出之前被提交之前调用,也就是即将挂载时调用

相当于淘宝购物的快照,会保留下单前的商品内容,在 React 中就相当于是 即将更新前的状态

它可以使组件在 DOM 真正更新之前捕获一些信息(例如滚动位置),此生命周期返回的任何值都会作为参数传递给 componentDidUpdate()。如不需要传递任何值,那么请返回 null

5. componentDidUpdate 执行

组件在更新完毕后会立即被调用,首次渲染不会调用


到此更新阶段就结束了,在 React 旧版本中有两个与更新有关的钩子函数 componentWillReceivePropscomponentWillUpdate 都即将废弃

componentWillReceiveProps 我不太懂

componentWillUpdaterender 之前执行,表示组件将要更新

销毁阶段

componentWillUnmount 执行

在组件即将被卸载或销毁时进行调用。

总结

初始化

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

更新

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

销毁

  • componentWillUnmount()

初学 React ,对生命周期还没有深入的理解,只能大概知道在什么时候触发哪个钩子,希望各位大佬多多指教,有什么建议可以提一提 🙏

React入门学习(四)– diffing 算法

前言

diff 算法是 React 提升渲染性能的一种优化算法,在 React 中有着很重要的地位,也不止于 React ,在 Vue 中也有 diff 算法,似乎没有差别。在最近的 React 学习中,学到了 diff 算法,感觉视频中的内容有点浅,对 diff 算法不够深入,因此想要深入的了解以下 diff 算法。于是在掘金,知乎,CSDN 等平台上,看了大量的博客,都非常地不错,可惜看不明白,wwww。所以这篇文章只是自己对于 diff 算法的一点理解,有什么问题或者错误的地方,大家一定要指出

什么是虚拟 DOM ?

在谈 diff 算法之前,我们需要先了解虚拟 DOM 。它是一种编程概念,在这个概念里,以一种虚拟的表现形式被保存在内存中。在 React 中,render 执行的结果得到的并不是真正的 DOM 节点,而是 JavaScript 对象

虚拟 DOM 只保留了真实 DOM 节点的一些基本属性,和节点之间的层次关系,它相当于建立在 JavaScript 和 DOM 之间的一层“缓存”

<div class="hello">
    <span>hello world!</span>
</div>

上面的这段代码会转化可以转化为虚拟 DOM 结构

&#123;
    tag: "div",
    props: &#123;
        class: "hello"
    &#125;,
    children: [&#123;
        tag: "span",
        props: &#123;&#125;,
        children: ["hello world!"]
    &#125;]
&#125;

其中对于一个节点必备的三个属性 tag,props,children

  • tag 指定元素的标签类型,如“lidiv
  • props 指定元素身上的属性,如 classstyle,自定义属性
  • children 指定元素是否有子节点,参数以数组形式传入

而我们在 render 中编写的 JSX 代码就是一种虚拟 DOM 结构。

什么是 diff 算法?

其实刚开始学习 React 的时候,很多人可能都听说过 React 很高效,性能很好这类的话语,这其实就是得益于 diff 算法和 Virturl DOM 的完美结合。

单纯的我刚开始会认为

React 也只不过是引入了别人的 diff 算法而已,能有多厉害,又不是原创 ?

但当我查阅了众多资料后,发现被提及最多的是一个 “传统 diff 算法”

其实 React 针对 diff 算法做出的优化,才是我们应当学习的

React 将原先时间复杂度为 O($n^3$) 的传统算法,优化到了 O(n)

大致执行过程图

image-20210824173128172

那 React 是如何实现的呢?

三个策略

为了将复杂度降到 O(n),React 基于这三个策略进行了算法优化

  1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
  3. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。

针对这三个策略,React 分别对 tree diffcomponent diff 以及 element diff 进行算法优化

tree diff 分层求异

首先会将新旧两个 DOM 树,进行比较,这个比较指的是分层比较。又由于 DOM 节点跨层级的移动操作很少,忽略不计。React 通过 updataDepth 对 虚拟 DOM 树进行层级控制,只会对同层节点进行比较,也就是图中只会对相同颜色方框内的 DOM 节点进行比较。例如:

当对比发现节点消失时,则该节点及其子节点都会被完全删除,不会进行更深层次的比较,这样只需要对树进行一次遍历,便能完成整颗 DOM 树的比较

image-20210824131810813

这里还有一个值得关注的地方:DOM 节点跨层级移动

为什么会提出这样的问题呢,在上面的删除原则中,我们发现当节点不存在了就会删除,那我只是给它换位了,它也会删除整个节点及其子节点吗?

image-20210824155347956

如图,我们需要实现这样的移动,你可能会以为它会直接这样移动

策略1

但是实际情况,并不是这样的。由于 React 只会简单的进行同层级节点位置变化,对于不同层级的节点,只有创建和删除操作,当发现 B 节点消失时,就会销毁 B,当发现 C 节点上多了 B 节点,就会创建 B 以及它的子节点。

因此这样会非常的复杂,所以 React 官方并不建议我们进行 DOM 节点跨级操作

component diff

在组件层面上,也进行了优化

  • 如果是同一类型的组件,则按照原策略继续比较 虚拟 DOM tree
  • 如果不是,则将这个组件记为 dirty component ,从而替换整个组件下的所有子节点

同时对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点就可以节省大量的 diff 运算的时间,因此 React 允许用户通过 shouldComponentUpdate() 判断该组件是否需要进行 diff 算法分析

总的来说,如果两个组件结构相似,但被认定为了不同类型的组件,则不会比较二者的结构,而是直接删除

element diff

element diff 是专门针对同一层级的所有节点的策略。当节点在同一层级时,diff 提供了 3个节点操作方法:插入,移动,删除

当我们要完成如图所示操作转化时,会有很大的困难,因为在新老节点比较的过程中,发现每个节点都要删除再重新创建,但是这只是重新排序了而已,对性能极大的不友好。因此 React 中提出了优化策略:

允许添加唯一值 key 来区分节点

image-20210824163240354

引入 key 的优化策略,让性能上有了翻天覆地的变化

那 key 有什么作用呢?

当同一层级的节点添加了 key 属性后,当位置发生变化时。react diff 进行新旧节点比较,如果发现有相同的 key 就会进行移动操作,而不会删除再创建

那 key 具体是如何起作用的呢?

首先在 React 中只允许节点右移

因此对于上图中的转化,只会进行 A,C 的移动

则只需要对移动的节点进行更新渲染,不移动的则不需要更新渲染

为什么不能用 index 作为 key 值呢?

index 作为 key ,如果我们删除了一个节点,那么数组的后一项可能会前移,这个时候移动的节点和删除的节点就是相同的 key ,在react中,如果 key 相同,就会视为相同的组件,但这两个组件是不同的,这样就会出现很麻烦的事情,例如:序号和文本不对应等问题

所以一定要保证 key 的唯一性

建议

React 已经帮我们做了很多了,剩下的需要我们多加注意,才能有更好的性能

基于三个策略我们需要注意

tree diff 建议:开发组件时,需要注意保持 DOM 结构稳定

component diff 建议:使用 shouldComponentUpdate() 来减少不要的更新

element diff 建议:减少最后一个节点移动到头部的操作,这样前面的节点都需要移动

参考资料

谈谈React中Diff算法的策略及实现

React diff算法

浅谈react 虚拟dom,diff算法与key机制


关于手写实现 diff 算法,还有点难度,这事等学完 React 后再说吧

非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈

React 入门学习(五)– 认识脚手架

简介

这篇文章主要围绕 React 中的脚手架,来解决一下几个问题

灵魂三问:是什么?为什么?怎么办?

  1. 什么是脚手架?
  2. 为什么要用脚手架?
  3. 怎么用脚手架?

🍕 1. 什么是 React 脚手架?

在我们的现实生活中,脚手架最常用的使用场景是在工地,它是为了保证施工顺利的、方便的进行而搭建的,在工地上搭建的脚手架可以帮助工人们高校的去完成工作,同时在大楼建设完成后,拆除脚手架并不会有任何的影响。

在我们的 React 项目中,脚手架的作用与之有异曲同工之妙

React 脚手架其实是一个工具帮我们快速的生成项目的工程化结构,每个项目的结构其实大致都是相同的,所以 React 给我提前的搭建好了,这也是脚手架强大之处之一,也是用 React 创建 SPA 应用的最佳方式

🍔 2. 为什么要用脚手架?

在前面的介绍中,我们也有了一定的认知,脚手架可以帮助我们快速的搭建一个项目结构

在我之前学习 webpack 的过程中,每次都需要配置 webpack.config.js 文件,用于配置我们项目的相关 loaderplugin,这些操作比较复杂,但是它的重复性很高,而且在项目打包时又很有必要,那 React 脚手架就帮助我们做了这些,它不需要我们人为的去编写 webpack 配置文件,它将这些配置文件全部都已经提前的配置好了。

据我猜测是直接输入一行命令就能打包完成。

目前还没有学习到哪,本文主要讲脚手架的项目目录结构以及安装

🍟 3. 怎么用 React 脚手架?

这也是这篇文章的重点,如何去安装 React 脚手架,并且理解它其中的相关文件作用

首先介绍如何安装脚手架

1. 安装 React 脚手架

首先确保安装了 npmNode,版本不要太古老,具体是多少不大清楚,建议还是用 npm update 更新一下

然后打开 cmd 命令行工具,全局安装 create-react-app

npm i create-react-app -g

然后可以新建一个文件夹用于存放项目

在当前的文件夹下执行

create-react-app hello-react

快速搭建项目

再在生成好的 hello-react 文件夹中执行

npm start

启动项目

接下来我们看看这些文件都有什么作用

2. 脚手架项目结构

hello-react
├─ .gitignore               // 自动创建本地仓库
├─ package.json             // 相关配置文件
├─ public                   // 公共资源
│  ├─ favicon.ico           // 浏览器顶部的icon图标
│  ├─ index.html            // 应用的 index.html入口
│  ├─ logo192.png           // 在 manifest 中使用的logo图
│  ├─ logo512.png           // 同上
│  ├─ manifest.json         // 应用加壳的配置文件
│  └─ robots.txt            // 爬虫给协议文件
├─ src                      // 源码文件夹
│  ├─ App.css               // App组件的样式
│  ├─ App.js                // App组件
│  ├─ App.test.js           // 用于给APP做测试
│  ├─ index.css             // 样式
│  ├─ index.js              // 入口文件
│  ├─ logo.svg              // logo图
│  ├─ reportWebVitals.js    // 页面性能分析文件
│  └─ setupTests.js         // 组件单元测试文件
└─ yarn.lock

再介绍一下public目录下的 index.html 文件中的代码意思

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

以上是删除代码注释后的全部代码

第5行

指定浏览器图标的路径,这里直接采用 %PUBLIC_URL% 原因是 webpack 配置好了,它代表的意思就是 public 文件夹

<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />

第6行

用于做移动端网页适配

<meta name="viewport" content="width=device-width, initial-scale=1" />

第七行

用于配置安卓手机浏览器顶部颜色,兼容性不大好

<meta name="theme-color" content="#000000" />

8到11行

用于描述网站信息

<meta
    name="description"
    content="Web site created using create-react-app"
/>

第12行

苹果手机触摸版应用图标

<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

第13行

应用加壳时的配置文件

<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

以上就是关于 React 脚手架的全部内容了,非常感谢你的阅读💕

React 入门学习(六)– TodoList 案例

引言

TodoList 案例在前端学习中挺重要的,从原生 JavaScript 的增删查改,到现在 React 的组件通信,都是一个不错的案例,这篇文章主要记录,还原一下通过 React 实现 TodoList 的全过程

image-20210826091013929

一、拆分组件

首先第一步需要做的是将这个页面拆分成几个组件

首先顶部的输入框,可以完成添加项目的功能,可以拆分成一个 Header 组件

中间部分可以实现一个渲染列表的功能,可以拆分成一个 List 组件

在这部分里面,每一个待办事项都可以拆分成一个 Item 组件

最后底部显示当前完成状态的部分,可以拆分成一个 Footer 组件

image-20210826092737826

在拆分完组件后,我们下一步要做的就是去实现这些组件的静态效果

二、实现静态组件

首先,我们可以先写好这个页面的静态页面,然后再分离组件,所以这就要求我们

以后写静态页面的时候,一定要有明确的规范

  1. 打好注释
  2. 每个部分的 CSS 要写在一个地方,不要随意写
  3. 命名一定要规范
  4. CSS 选择器不要关联太多层级
  5. 在写 HTML 时就要划分好布局

这样有利于我们分离组件

首先,我们在 src 目录下,新建一个 Components 文件夹,用于存放我们的组件,然后在文件夹下,新建 HeaderItemListFooter 组件文件夹,再创建其下的 index.jsxindex.css 文件,用于创建对应组件及其样式文件

todolist
├─ package.json
├─ public
│  ├─ favicon.ico
│  └─ index.html
├─ src
│  ├─ App.css
│  ├─ App.jsx
│  ├─ Components
│  │  ├─ Footer
│  │  │  ├─ index.css
│  │  │  └─ index.jsx
│  │  ├─ Header
│  │  │  ├─ index.css
│  │  │  └─ index.jsx
│  │  ├─ item
│  │  │  ├─ index.css
│  │  │  └─ index.jsx
│  │  └─ List
│  │     ├─ index.css
│  │     └─ index.jsx
│  └─ index.js
└─ yarn.lock

最终目录结构如上

然后我们将每个组件,对应的 HTML 结构 CV 到对应组件的 index.jsx 文件中 return 出来,再将 CSS 样式添加到 index.css 文件中

记得,在 index.jsx 中一定要引入 index.css 文件

实现了静态组件后,我们需要添加事件等,来实现动态组件

三、实现动态组件

🍎 1. 动态展示列表

我们目前实现的列表项是固定的,我们需要它通过状态来维护,而不是通过组件标签来维护

首先我们知道,父子之间传递参数,可以通过 stateprops 实现

我们通过在父组件也就是 App.jsx 中设置状态

image-20210826103418053

再将它传递给对应的渲染组件 List

const &#123; todos &#125; = this.state
<List todos=&#123;todos&#125;/>

这样在 List 组件中就能通过 props 来获取到 todos

我们通过解构取出 todos

const &#123; todos, updateTodo &#125; = this.props

再通过 map 遍历渲染 Item 数量

&#123;
  todos.map(todo => &#123;
    return <Item key=&#123;todo.id&#125; &#123;...todo&#125;/>
  &#125;)
&#125;

同时由于我们的数据渲染最终是在 Item 组件中完成的,所以我们需要将数据传递给 Item 组件

这里有两个注意点

  1. 关于 key 的作用在 diff 算法的文章中已经有讲过了,需要满足唯一性
  2. 这里采用了简写形式 {...todo} ,这使得代码更加简洁,它代表的意思是
id = &#123;todo.id&#125; name = &#123;todo.name&#125; done = &#123;todo.done&#125;

Item 组件中取出 props 即可使用

const &#123; id, name, done &#125; = this.props

这样我们更改 APP.jsx 文件中的 state 就能驱动着 Item 组件的更新,如图

react-todolist-1

同时这里需要注意的是

对于复选框的选中状态,这里采用的是 defaultChecked = {done},相比于 checked 属性,这个设定的是默认值,能够更改

🍍 2. 添加事项功能

首先我们需要在 Header 组件中,绑定键盘事件,判断按下的是否为回车,如果为回车,则将当前输入框中的内容传递给 APP 组件

因为,在目前的学习知识中,Header 组件和渲染组件 List 属于兄弟组件,没有办法进行直接的数据传递,因此可以将数据传递给 APP 再由 APP 转发给 List。

// Header/index.jsx
handleKeyUp = (event) => &#123;
  // 结构赋值获取 keyCode,target
  const &#123; keyCode, target &#125; = event
  // 判断是不是回车
  if (keyCode !== 13) return
  if(target.value.trim() === '') &#123;
    alert('输入不能为空')
  &#125;
  // 准备一个todo对象
  const todoObj = &#123; id: nanoid(), name: target.value, done: false &#125;
  // 传递给app
  this.props.addTodo(todoObj)
  // 清空
  target.value = ''
&#125;

我们在 App.jsx 中添加了事件 addTodo ,这样可以将 Header 组件传递的参数,维护到 App 的状态中

// App.jsx
addTodo = (todoObj) => &#123;
  const &#123; todos &#125; = this.state
  // 追加一个 todo
  const newTodos = [todoObj, ...todos]
  this.setState(&#123; todos: newTodos &#125;)
&#125;

在这小部分中,需要我们注意的是,我们新建的 todo 对象,一定要保证它的 id 的唯一性

这里采用的 nanoid 库,这个库的每一次调用都会返回一个唯一的值

npm i nanoid

安装这个库,然后引入

通过 nanoid() 即可生成唯一值

react-todolist-addtodo

🍋 3. 实现鼠标悬浮效果

接下来我们需要实现每个 Item 中的小功能

首先是鼠标移入时的变色效果

我的逻辑是,通过一个状态来维护是否鼠标移入,比如用一个 mouse 变量,值给 false 当鼠标移入时,重新设定状态为 true 当鼠标移出时设为 false ,然后我们只需要在 style 中用mouse 去设定样式即可

下面我们来代码实现

Item 组件中,先设定状态

state = &#123; mouse: false &#125; // 标识鼠标移入,移出

给元素绑定上鼠标移入,移出事件

<li onMouseEnter=&#123;this.handleMouse(true)&#125; onMouseLeave=&#123;this.handleMouse(false)&#125; ><li/>

当鼠标移入时,会触发 onMouseEnter 事件,调用 handleMouse 事件传入参数 true 表示鼠标进入,更新组件状态

handleMouse = flag => &#123;
    return () => &#123;
        this.setState(&#123; mouse: flag &#125;)
    &#125;
&#125;

再在 li 身上添加由 mouse 控制的背景颜色

style=&#123;&#123; backgroundColor: this.state.mouse ? '#ddd' : 'white' &#125;&#125;

同时通过 mouse 来控制删除按钮的显示和隐藏,做法和上面一样

react-todolist-mouse

观察 mouse 的变化

🍉 4. 复选框状态维护

我们需要将当前复选框的状态,维护到 state 当中

我们的思路是

在复选框中添加一个 onChange 事件来进行数据的传递,当事件触发时我们执行 handleCheck 函数,这个函数可以向 App 组件中传递参数,这样再在 App 中改变状态即可

首先绑定事件

// Item/index.jsx
<input type="checkbox" defaultChecked=&#123;done&#125; onChange=&#123;this.handleCheck(id)&#125; />

事件回调

handleCheck = (id) => &#123;
    return (event) => &#123;
        this.props.updateTodo(id, event.target.checked)
    &#125;
&#125;

由于我们需要传递 id 来记录状态更新的对象,因此我们需要采用高阶函数的写法,不然函数会直接执行而报错,复选框的状态我们可以通过 event.target.checked 来获取

这样我们将我们需要改变状态的 Itemid 和改变后的状态,传递给了 App

内定义的updateTodo 事件,这样我们可以在 App 组件中操作改变状态

我们传递了两个参数 iddone

通过遍历找出该 id 对应的 todo 对象,更改它的 done 即可

// App.jsx
updateTodo = (id, done) => &#123;
  const &#123; todos &#125; = this.state
  // 处理
  const newTodos = todos.map(todoObj => &#123;
    if (todoObj.id === id) &#123;
      return &#123; ...todoObj, done &#125;
    &#125; else &#123;
      return todoObj
    &#125;
  &#125;)
  this.setState(&#123; todos: newTodos &#125;)
&#125;

这里更改的方式是 { ...todoObj, done },首先会展开 todoObj 的每一项,再对 done 属性做覆盖

react-todolist-update

🍏 5. 限制参数类型

在我们前面写的东西中,我们并没有对参数的类型以及必要性进行限制

在前面我们也学过这个,我们需要借助 propTypes 这个库

首先我们需要引入这个库,然后对 props 进行限制

// Header
static propTypes = &#123;
  addTodo: PropTypes.func.isRequired
&#125;

在Header 组件中需要接收一个 addTodo 函数,所以我们进行一下限制

同时在 List 组件中也需要进行对 todos 以及 updateTodo 的限制

如果传入的参数不符合限制,则会报 warning

🍒 6. 删除按钮

现在我们需要实现删除按钮的效果

这个和前面的挺像的,首先我们分析一下,我们需要在 Item 组件上的按钮绑定点击事件,然后传入被点击事项的 id 值,通过 props 将它传递给父元素 List ,再通过在 List 中绑定一个 App 组件中的删除回调,将 id 传递给 App 来改变 state

首先我们先编写 点击事件

// Item/index.jsx
handleDelete = (id) => &#123;
    this.props.deleteTodo(id)
&#125;

绑定在点击事件的回调上

子组件想影响父组件的状态,需要父组件传递一个函数,因此我们在 App 中添加一个 deleteTodo 函数

// app.jsx
deleteTodo = (id) => &#123;
  const &#123; todos &#125; = this.state
  const newTodos = todos.filter(todoObj => &#123;
    return todoObj.id !== id
  &#125;)
  this.setState(&#123; todos: newTodos &#125;)
&#125;

然后将这个函数传递给 List 组件,再传递给 Item

增加一个判断

if(window.confirm('确认删除')) &#123;
    this.props.deleteTodo(id)
&#125;

react-todolist-detele

🍓 7. 获取完成数量

我们在 App 中向 Footer 组件传递 todos 数据,再去统计数据

统计 done true 的个数

const doneCount = todos.reduce((pre, todo) => &#123;
    return pre + (todo.done ? 1 : 0)
&#125;, 0)

再渲染数据即可

image-20210826160505813

🍊 8. 全选按钮

首先我们需要在按钮上绑定事件,由于子组件需要改变父组件的状态,所以我们的操作和之前的一样,先绑定事件,再在 App 中传一个函数个 Footer ,再在 Footer 中调用这个函数并传入参数即可

这里需要特别注意的是

defaulChecked 只有第一次会起作用,所以我们需要将前面写的改成 checked 添加 onChange 事件即可

首先我们先在 App 中给 Footer 传入一个函数 checkAllTodo

// App.jsx
checkAllTodo = (done) => &#123;
  const &#123; todos &#125; = this.state
  const newTodos = todos.map((todoObj => &#123;
    return &#123; ...todoObj, done: done &#125;
  &#125;))
  this.setState(&#123; todos: newTodos &#125;)
&#125;
// render
 <Footer todos=&#123;todos&#125; checkAllTodo=&#123;this.checkAllTodo&#125;/>

然后我们需要在 Footer 中调用一下

handleCheckAll = (event) => &#123;
    this.props.checkAllTodo(event.target.checked)
&#125;

这里我们传入了一个参数:当前按钮的状态,用于全选和取消全选

同时我们需要排除总数为0 时的干扰

<input type="checkbox" checked=&#123;doneCount === total && total !== 0? true : false&#125; onChange=&#123;this.handleCheckAll&#125; />

react-todolist-all

🥭 9. 删除已完成

给删除按钮添加一个点击事件,回调中调用 App 中添加的删除已完成的函数,全都一个套路

强烈建议这个自己打

首先在 Footer 组件中调用传来的函数,在 App 中定义函数,过滤掉 donetrue 的,再更新状态即可

// App.jsx
clearAllDone = () => &#123;
  const &#123; todos &#125; = this.state
  const newTodos = todos.filter((todoObj) => &#123;
    return todoObj.done !== true
  &#125;)
  this.setState(&#123; todos: newTodos &#125;)
&#125;

react-todolist-clear

总结

  1. 注意:className、style 写法
  2. 父组件给子组件传递数据,采用 props
  3. 子组件给父组件传递数据,通过 props,同时提前给子组件传递一个函数
  4. 注意 defaultCheckedchecked 的区别
  5. 一定要自己敲一下,好好理解数据传递

非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈

React 入门学习(七)– 脚手架配置代理

引言

React 本身只关注于页面,并不包含发送 Ajax 请求的代码,所以一般都是集成第三方的包,或者自己封装的

自己封装的话,比较麻烦,而且也可能考虑不全

常用的有两个库,一个是JQuery,一个是 axios

  1. JQuery 这个比较重,因为 Ajax 服务也只是它这个库里的一小块功能,它主要做的还是 DOM 操作,而这不利于 React ,不推荐使用
  2. axios 这个就比较轻,而且采用 Promise 风格,代码的逻辑会相对清晰,推荐使用

因此我们这里采用 axios 来发送客户端请求

以前,我们在发送请求的时候,经常会遇到一个很重要的问题:跨域!

image-20210827091119837

在我以前的学习中,基本上都需要操作后端服务器代码才能解决跨域的问题,配置请求头,利用 script,这些都需要后端服务器的配合,因此我们前端需要自己解决这个问题的话,就需要这个技术了:代理

在说代理之前,先谈谈为什么会出现跨域?

这个应该是源于浏览器的同源策略。所谓同源(即指在同一个域)就是两个页面具有相同的协议,主机和端口号, 当一个请求 URL 的协议、域名、端口三者之间任意一个与当前页面 URL 不同即为跨域 。

也就是说 xxx:3000xxx:4000 会有跨域问题,xxx:3000abc:3000 有跨域问题

那接下来我们采用配置代理的方式去解决这个问题

关于跨域的问题解决方案,在之后的文章会有总结 ~

1. 全局代理

第一种方法,我把它叫做全局代理,因为它直接将代理配置在了配置文件 package.json

"proxy":"http://localhost:5000"  
// "proxy":"请求的地址"

这样配置代理时,首先会在抓原请求地址上访问,如果访问不到文件,就会转发到这里配置的地址上去请求

转发

我们需要做的就是在我们的请求代码中,将请求的地址改到转发的地址,即可

但是这样会有一些问题,它会先向我们请求的地址,也就是这里的 3000 端口下请求数据,如果在 3000 端口中存在我们需要访问的文件,会直接返回,不会再去转发

因此这就会出现问题,同时因为这种方式采用的是全局配置的关系,导致只能转发到一个地址,不能配置多个代理

2. 单独配置

这也是我自己起的名字,这种配置方式,可以给多个请求配置代理,非常不错

它的工作原理和全局配置是一样的,但是写法不同

首先我们需要在 src 目录下,创建代理配置文件 setupProxy.js

注意:这个文件只能叫这个名字,脚手架在启动的时候,会自动执行这些文件

第二步

配置具体的代理规则,我们大致讲讲这些是什么意思

  1. 首先我们需要引入这个 http-proxy-middleware 中间件,然后需要导出一个对象,这里建议使用函数,使用对象的话兼容性不大好

  2. 然后我们需要在 app.use 中配置,我们的代理规则,首先 proxy 接收的第一个参数是需要转发的请求,我的理解是一个标志的作用,当有这个标志的时候,预示着我们需要采用代理,例如 /api1 ,我们就需要在我们 axios 的请求路径中,加上 /api1 ,这样所有添加了 /api1 前缀的请求都会转发到这

  3. 第二个参数接受的是一个对象,用于配置代理。

    • target 属性用于配置转发目标地址,也就是我们数据的地址
    • changeOrigin 属性用于控制服务器收到的请求头中 host 字段,可以理解为一个伪装效果,为 true 时,收到的 host 就为请求数据的地址
    • pathRewrite 属性用于去除请求前缀,因为我们通过代理请求时,需要在请求地址前添加一个标志,但是实际的地址是不存在这个标志的,所以我们一定要去除这个前缀,这里采用的有点类似于正则替换的方式

配置一个代理的完整代码如下

const proxy = require('http-proxy-middleware')
module.exports = function(app) &#123;
  app.use(
    proxy('/api1', &#123; 
      target: 'http://localhost:5000', //配置转发目标地址
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      pathRewrite: &#123;'^/api1': ''&#125; //去除请求前缀址(必须配置)
    &#125;),
  )
&#125;

关于脚手架配置代理的内容就到这里啦!

非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈

React 入门学习(八)– GitHub 搜索案例

引言

本文主要介绍 React 学习中 Github 搜索案例,这个案例主要涉及到了 Axios 发送请求,数据渲染以及一些中间交替效果的实现

个人感觉在做完 TodoList 案例之后,这个案例会很轻松,只是多加了一个 Loading 效果的实现思路,以及一些小细节的完善,感觉练练手还是很不错的

一、实现静态组件

和之前的 TodoList 案例一样,我们需要先实现静态组件,在实现静态组件之前,我们还需要拆分组件,这个页面的组件,我们可以将它拆成以下两个组件,第一个组件是 Search,第二个是 List

image-20210828065604542

接下来我们需要将提前写好的静态页面,对应拆分到组件当中

注意:

  1. class 需要改成 className
  2. style 的值需要使用双花括号的形式

最重要的一点就是,img 标签,一定要添加 alt 属性表示图片加载失败时的提示。

同时,a 标签要添加 rel="noreferrer"属性,不然会有大量的警告出现

image-20210828070148865

二、axios 发送请求

在实现静态组件之后,我们需要通过向 github 发送请求,来获取相应的用户信息

但是由于短时间内多次请求,可能会导致请求不返回结果等情况发生,因此我们采用了一个事先搭建好的本地服务器

我们启动服务器,向这个地址发送请求即可

image-20210828071053508

这个请求类型是 GET 请求,我们需要传递一个搜索的关键字,去请求数据

我们首先要获取到用户点击搜索按钮后输入框中的值

在需要触发事件的 input 标签中,添加 ref 属性

 <input ref=&#123;c => this.keyWordElement = c&#125; type="text" placeholder="输入关键词点击搜索" />

我们可以通过 this.keyWordElement 属性来获取到这个当前节点,也就是这个 input

我们再通过 value 值,即可获取到当前 input 框中的值

// search 回调
const &#123; keyWordElement: &#123; value: keyWord &#125; &#125; = this

这里采用的是连续的解构赋值,最后将 value 改为 keyWord ,这样好辨别

获取到了 keyWord 值,接下来我们就需要发送请求了

axios.get(`http://localhost:3000/api1/search/users?q=$&#123;keyWord&#125;`).then(
    response => &#123;
        this.props.updateAppState(&#123; isLoading: false, users: response.data.items &#125;)
    &#125;,
    error => &#123;
        this.props.updateAppState(&#123; isLoading: false, err: error.message &#125;)
    &#125;
)

我们将 keyWord 接在请求地址的后面,来传递参数,以获得相关数据

这里会存在跨域的问题,因我我们是站在 3000 端口向 5000 端口发送请求的

因此我们需要配置代理来解决跨域的问题,我们需要在请求地址前,加上启用代理的标志 /api1

// setupProxy.js
const proxy = require('http-proxy-middleware')
module.exports = function (app) &#123;
    app.use(
        proxy('/api1', &#123;
            target: 'http://localhost:5000',
            changeOrigin: true,
            pathRewrite: &#123;
                '^/api1': ''
            &#125;
        &#125;)
    )
&#125;

这样我们就能成功的获取到了数据

image-20210828072705747

三、渲染数据

在获取到了数据之后,我们需要对数据进行分析,并将这些数据渲染到页面上

比较重要的一点是,我们获取到的用户个数是动态的,因此我们需要通过遍历的方式去实现

同时我们的数据当前存在于 Search 组件当中,我们需要在 List 组件中使用,所以我们需要个 Search 组件传递一个函数,来实现子向父传递数据,再通过 App 组件,向List 组件传递数据即可得到 data

users.map((userObj) => &#123;
  return (
    <div key=&#123;userObj.id&#125; className="card">
      <a rel="noreferrer" href=&#123;userObj.html_url&#125; target="_blank">
        <img alt="avatar" src=&#123;userObj.avatar_url&#125; style=&#123;&#123; width: '100px' &#125;&#125; />
      </a>
      <p className="card-text">&#123;userObj.login&#125;</p>
    </div>
  )
&#125;)

这里我们通过 map 遍历整个返回的数据,来循环的添加 card 的个数

同时将一些用户信息添加到其中

四、增加交互

做到这里其实已经完成了一大半了,但是似乎少了点交互

  • 加载时的 loading 效果
  • 第一次进入页面时 List 组件中的欢迎使用字样
  • 在报错时应该提示错误信息

这一些都预示着我们不能单纯的将用户数据直接渲染,我们需要添加一些判断,什么时候该渲染数据,什么时候渲染 loading,什么时候渲染 err

首先我们需要增加一些状态,来指示我们该渲染什么,比如

  • 采用 isFrist 来判断页面是否第一次启动,初始值给 true,点击搜索后改为 false
  • 采用 isLoading 来判断是否应该显示 Loading 动画,初始值给 false,在点击搜索后改为 true,在拿到数据后改为 false
  • 采用 err 来判断是否渲染错误信息,当报错时填入报错信息,初始值给空
state = &#123; users: [], isFirst: true, isLoading: false, err: '' &#125;

这样我们就需要改变我先前采用的数据传递方式,采用更新状态的方式,接收一个状态对象来更新数据,这样就不用去指定什么时候更新什么,就可以减少很多不必要的函数声明

同时在 App 组件给 List 组件传递数据时,我们可以采用解构赋值的方式,这样可以减少代码量

// App.jsx
// 接收一个状态对象
updateAppState = (stateObj) => &#123;
    this.setState(stateObj)
&#125;
<Search updateAppState=&#123;this.updateAppState&#125; />
<List &#123;...this.state&#125; />

这样我们只需要在 List 组件中,判断这些状态的值,来显示即可

// List/index.jsx
// 对象解构
const &#123; users, isFirst, isLoading, err &#125; = this.props
// 判断
&#123;
  isFirst ? <h2>欢迎使用,输入关键字,点击搜索</h2> :
    isLoading ? <h2>Loading...</h2> :
      err ? <h2 style=&#123;&#123; color: 'red' &#125;&#125;>&#123;err&#125;</h2> :
        users.map((userObj) => &#123;
          return (
           // 渲染数据块
           //为了减少代码量,就不贴了
          )
        &#125;)
&#125;

我们需要先判断是否第一次,再判断是不是正在加载,再判断有没有报错,最后再渲染数据

我们的状态更新是在 Search 组件中实现的,在点击搜索之后数据返回之前,我们需要将 isFirst 改为 falseisLoading 改为 true

接收到数据后我们再将 isLoading 改为 false 即可

以上就是 Github 搜索案例的实现过程

react-github

最终效果图


前端路还有很长,今天我就大二啦!加油吧!!!

非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈

React 入门学习(九)– 消息订阅发布

引言

在昨天写的 Github 案例中,我们采用的是 axios 发送请求来获取数据,同时我们需要将数据从 Search 中传入给 App,再由 App 组件再将数据传递给 List 组件,这个过程会显得多此一举。同时我们要将 state 状态存放在 App 组件当中,但是这些 state 状态都是在 List 组件中使用的,在 Search 组件中做的,只是更新这些数据,那这样也会显得很没有必要,我们完全可以将 state 状态存放在 List 组件中,但是这样我们又会遇到技术难题,兄弟组件间的数据通信。那这里我们就学习一下如何利用消息订阅发布来解决兄弟组件间的通信

消息发布订阅

要解决上面的问题,我们可以借助发布订阅的机制,我们可以将 App 文件中的所有状态和方法全部去除,因为本来就不是在 App 组件中直接使用这些方法的,App 组件只是一个中间媒介而已

我们先简单的说一下消息订阅和发布的机制

就拿我们平常订杂志来说,我们和出版社说我们要订一年的足球周刊,那每次有新的足球周刊,它都会寄来给你。

换到代码层面上,我们订阅了一个消息假设为 A,当另一个人发布了 A 消息时,因为我们订阅了消息 A ,那么我们就可以拿到 A 消息,并获取数据

那我们要怎么实现呢?

首先引入 pubsub-js

我们需要先安装这个库

yarn add pubsub-js

引入这个库

import PubSub from 'pubsub-js'

订阅消息

我们通过 subscribe 来订阅消息,它接收两个参数,第一个参数是消息的名称,第二个是消息成功的回调,回调中也接受两个参数,一个是消息名称,一个是返回的数据

PubSub.subscribe('search',(msg,data)=>&#123;
  console.log(msg,data);
&#125;)

发布消息

我们采用 publish 来发布消息,用法如下

PubSub.publish('search',&#123;name:'tom',age:18&#125;)

有了这些基础,我们可以完善我们昨天写的 GitHub 案例

将数据的更新通过 publish 来传递,例如在发送请求之前,我们需要出现 loading 字样

// 之前的写法
this.props.updateAppState(&#123; isFirst: false, isLoading: true &#125;)
// 改为发布订阅方式
PubSub.publish('search',&#123; isFirst: false, isLoading: true &#125;)

这样我们就能成功的在请求之前发送消息,我们只需要在 List 组件中订阅一下这个消息即可,并将返回的数据用于更新状态即可

PubSub.subscribe('search',(msg,stateObj)=>&#123;
  this.setState(stateObj)
&#125;)

同时上面的代码会返回一个 token ,这个就类似于定时器的编号的存在,我们可以通过这个 token 值,来取消对应的订阅

通过 unsubscribe 来取消指定的订阅

PubSub.unsubscribe(this.token)

扩展 – Fetch

首先 fetch 也是一种发送请求的方式,它是在 xhr 之外的一种,我们平常用的 Jquery 和 axios 都是封装了 xhr 的第三方库,而 fetch 是官方自带的库,同时它也采用的是 Promise 的方式,大大简化了写法

如何使用呢?

fetch('http://xxx')
  .then(response => response.json())
  .then(json => console.log(json))
  .catch(err => console.log('Request Failed', err)); 

它的使用方法和 axios 非常的类似,都是返回 Promise 对象,但是不同的是, fetch 关注分离,它在第一次请求时,不会直接返回数据,会先返回联系服务器的状态,在第二步中才能够获取到数据

我们需要在第一次 then 中返回 response.json() 因为这个是包含数据的 promise 对象,再调用一次 then 方法即可实现

但是这么多次的调用 then 并不是我们所期望的,相信看过之前生成器的文章的伙伴,已经有了想法。

我们可以利用 asyncawait 配合使用,来简化代码

可以将 await 理解成一个自动执行的 then 方法,这样清晰多了

async function getJSON() &#123;
  let url = 'https://xxx';
  try &#123;
    let response = await fetch(url);
    return await reasponse.json();
  &#125; catch (error) &#123;
    console.log('Request Failed', error);
  &#125;
&#125;

最后关于错误对象的获取可以采用 try...catch 来实现

关于 fetch 的更多内容

强烈推荐阮一峰老师的博文:fetch


非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈

🌮 React 入门学习(十)– React 路由

引言

在我们之前写的页面当中,用我们的惯用思维去思考的话,可能会需要写很多的页面,例如做一个 tab 栏,我们可能会想每个选项都要对应一个 HTML 文件,这样会很麻烦,甚至不友好,我们把这种称为 MPA 也叫多页面应用。

🍕 1. SPA

而为了减少这样的情况,我们还有另一种应用,叫做 SPA ,单页应用程序

它比传统的 Web 应用程序更快,因为它们在 Web 浏览器本身而不是在服务器上执行逻辑。在初始页面加载后,只有数据来回发送,而不是整个 HTML,这会降低带宽。它们可以独立请求标记和数据,并直接在浏览器中呈现页面

🍔 2. 什么是路由?

路由是根据不同的 URL 地址展示不同的内容或页面

在 SPA 应用中,大部分页面结果不改变,只改变部分内容的使用

前端路由的优缺点

优点

用户体验好,不需要每次都从服务器全部获取整个 HTML,快速展现给用户

缺点

  1. SPA 无法记住之前页面滚动的位置,再次回到页面时无法记住滚动的位置
  2. 使用浏览器的前进和后退键会重新请求,没有合理利用缓存

🍟 3. 路由的原理

前端路由的主要依靠的时 history ,也就是浏览器的历史记录

history 是 BOM 对象下的一个属性,在 H5 中新增了一些操作 history 的 API

浏览器的历史记录就类似于一个栈的数据结构,前进就相当于入栈,后退就相当于出栈

并且历史记录上可以采用 listen 来监听请求路由的改变,从而判断是否改变路径

在 H5 中新增了 createBrowserHistory 的 API ,用于创建一个 history 栈,允许我们手动操作浏览器的历史记录

新增 API:pushStatereplaceState,原理类似于 Hash 实现。 用 H5 实现,单页路由的 URL 不会多出一个 # 号,这样会更加的美观

🌭 4. 路由的基本使用

react-router-dom 的理解和使用

专门给 web 人员使用的库

  1. 一个 react 的仓库
  2. 很常用,基本是每个应用都会使用的这个库
  3. 专门来实现 SPA 应用

首先我们要明确好页面的布局 ,分好导航区、展示区

要引入 react-router-dom 库,暴露一些属性 Link、BrowserRouter...

import &#123; Link, BrowserRouter, Route &#125; from 'react-router-dom'

导航区的 a 标签改为 Link 标签

<Link className="list-group-item" to="/about">About</Link>

同时我们需要用 Route 标签,来进行路径的匹配,从而实现不同路径的组件切换

<Route path="/about" component=&#123;About&#125;></Route>
<Route path="/home" component=&#123;Home&#125;></Route>

这样之后我们还需要一步,加个路由器,在上面我们写了两组路由,同时还会报错指示我们需要添加 Router 来解决错误,这就是需要我们添加路由器来管理路由,如果我们在 Link 和 Route 中分别用路由器管理,那这样是实现不了的,只有在一个路由器的管理下才能进行页面的跳转工作。

因此我们也可以在 Link 和 Route 标签的外层标签采用 BrowserRouter 包裹,但是这样当我们的路由过多时,我们要不停的更改标签包裹的位置,因此我们可以这么做

我们回到 App.jsx 目录下的 index.js 文件,将整个 App 组件标签采用 BrowserRouter 标签去包裹,这样整个 App 组件都在一个路由器的管理下

// index.js
<BrowserRouter>
< App />
</BrowserRouter>

react-router

🍿 5. 路由组件和一般组件

在我们前面的内容中,我们是把组件 Home 和组件 About 当成是一般组件来使用,我们将它们写在了 src 目录下的 components 文件夹下,但是我们又会发现它和普通的组件又有点不同,对于普通组件而言,我们在引入它们的时候我们是通过标签的形式来引用的。但是在上面我们可以看到,我们把它当作路由来引用时,我们是通过 {Home} 来引用的。

从这一点我们就可以认定一般组件和路由组件存在着差异

首先它们的写法不同

一般组件<Demo/>路由组件<Route path="/demo" component={Demo}/>

同时为了规范我们的书写,一般将路由组件放在 pages 文件夹中,路由组件放在 components

而最重要的一点就是它们接收到的 props 不同,在一般组件中,如果我们不进行传递,就不会收到值。而对于路由组件而言,它会接收到 3 个固定属性 historylocation 以及 match

image-20210901142121047

NavLink 标签是和 Link 标签作用相同的,但是它又比 Link 更加强大。

在前面的 demo 展示中,你可能会发现点击的按钮并没有出现高亮的效果,正常情况下我们给标签多添加一个 active 的类就可以实现高亮的效果

而 NavLink 标签正可以帮助我们实现这一步

当我们选中某个 NavLink 标签时,就会自动的在类上添加一个 active 属性

<NavLink className="list-group-item" to="/about">About</NavLink>

react-router-navlink

我们可以看到左侧的元素类名在不断的切换,当然 NavLink 标签是默认的添加上 active 类,我们也可以改变它,在标签上添加一个属性 activeClassName

例如 activeClassName="aaa" 在触发这个 NavLink 时,会自动添加一个 aaa

在上面的 NavLink 标签种,我们可以发现我们每次都需要重复的去写这些样式名称或者是 activeClassName ,这并不是一个很好的情况,代码过于冗余。那我们是不是可以想想办法封装一下它们呢?

我们可以采用 MyNavLink 组件,对 NavLink 进行封装

首先我们需要新建一个 MyNavLink 组件

return 一个结构

<NavLink className="list-group-item" &#123;...this.props&#125; />

首先,有一点非常重要的是,我们在标签体内写的内容都会成为一个 children 属性,因此我们在调用 MyNavLink 时,在标签体中写的内容,都会成为 props 中的一部分,从而能够实现

接下来我们在调用时,直接写

<MyNavLink to="/home">home</MyNavLink>

即可实现相同的效果


以上就是本节关于 React 路由的相关知识!

非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈

🌮 React 入门学习(十一)– React 路由传参

引言

在上一篇中,我们学习了 React 中使用路由技术,以及如何使用 MyNavLink 去优化使用路由时的代码冗余的情况。

这一节我们继续上一篇 React 路由进行一些补充

🍈 1. Switch 解决相同路径问题

首先我们看一段这样的代码

<Route path="/home" component=&#123;Home&#125;></Route>
<Route path="/about" component=&#123;About&#125;></Route>
<Route path="/about" component=&#123;About&#125;></Route>

这是两个路由组件,在2,3行中,我们同时使用了相同的路径 /about

image-20210903075753268

我们发现它出现了两个 about 组件的内容,那这是为什么呢?

其实是因为,Route 的机制,当匹配上了第一个 /about 组件后,它还会继续向下匹配,因此会出现两个 About 组件,这时我们可以采用 Switch 组件进行包裹

<Switch>
    <Route path="/home" component=&#123;Home&#125;></Route>
    <Route path="/about" component=&#123;About&#125;></Route>
    <Route path="/about" component=&#123;About&#125;></Route>
</Switch>

在使用 Switch 时,我们需要先从 react-router-dom 中暴露出 Switch 组件

这样我们就能成功的解决掉这个问题了

🥟 2. 解决二级路由样式丢失的问题

当我们将路径改写成 path="/ljc/about" 这样的形式时,我们会发现当我们强制刷新页面的时候,页面的 CSS 样式消失了。这是因为,我们在引入样式文件时,采取的是相对路径,当我们使用二级路由的时候,会使得请求的路径发生改变,浏览器会向 localhost:3000/ljc 下请求 css 样式资源,这并不是我们想要的,因为我们的样式存放于公共文件下的 CSS 文件夹中。

react-router-tworouter

我们有几种方法,可以解决这个问题

  1. 将样式引入的路径改成绝对路径
  2. 引入样式文件时不带 .
  3. 使用 HashRouter

我们一般采用第一种方式去解决

🍑 3. 路由的精准匹配和模糊匹配

路由的匹配有两种形式,一种是精准匹配一种是模糊匹配,React 中默认开启的是模糊匹配

模糊匹配可以理解为,在匹配路由时,只要有匹配到的就好了

精准匹配就是,两者必须相同

我们展示一个模糊匹配的例子

<MyNavLink to = "/home/a/b" >Home</MyNavLink>

这个标签匹配的路由,我们可以拆分成 home a b,将会根据这个先后顺序匹配路由

<Route path="/home"component=&#123;Home&#125;/>

就可以匹配到上面的这个路由,因为它匹配的是 home

当匹配的路由改成下面这样时,就会失败。它会按照第一个来匹配,如果第一个没有匹配上,那就会失败,这里的 a 和 home 没有匹配上,很显然会失败

<Route path="/a" component=&#123;Home&#125;/>

当我们开启了精准匹配后,就我们的第一种匹配就不会成功,因为精准匹配需要的是完全一样的值,开启精准匹配采用的是 exact 来实现

<Route exact=&#123;true&#125;  path="/home" component=&#123;Home&#125;/>

🍋 4. 重定向路由

在我们写好了这些之后,我们会发现,我们需要点击任意一个按钮,才会去匹配一个组件,这并不是我们想要的,我们想要页面一加载上来,默认的就能匹配到一个组件。

这个时候我们就需要时候 Redirecrt 进行默认匹配了。

<Redirect to="/home" />

当我们加上这条语句时,页面找不到指定路径时,就会重定向到 /home 页面下因此当我们请求3000端口时,就会重定向到 /home 这样就能够实现我们想要的效果了

image-20210904013342960

🍓 5. 嵌套路由

嵌套路由也就是我们前面有提及的二级路由,但是嵌套路由包括了二级、三级…还有很多级路由,当我们需要在一个路由组件中添加两个组件,一个是头部,一个是内容区

我们将我们的嵌套内容写在相应的组件里面,这个是在 Home 组件的 return 内容

<div>
    <h2>Home组件内容</h2>
    <div>
        <ul className="nav nav-tabs">
            <li>
                <MyNavLink className="list-group-item" to="/home/news">News</MyNavLink>
            </li>
            <li>
                <MyNavLink className="list-group-item " to="/home/message">Message</MyNavLink>
            </li>
        </ul>
        &#123;/* 注册路由 */&#125;
        <Switch>
            <Route path="/home/news" component=&#123;News&#125; />
            <Route path="/home/message" component=&#123;Message&#125; />
        </Switch>
    </div>
</div>

在这里我们需要使用嵌套路由的方式,才能完成匹配

首先我们得 React 中路由得注册是有顺序得,我们在匹配得时候,因为 Home 组件是先注册得,因此在匹配的时候先去找 home 路由,由于是模糊匹配,会成功的匹配

在 Home 组件里面去匹配相应的路由,从而找到 /home/news 进行匹配,因此找到 News 组件,进行匹配渲染

如果开启精确匹配的话,第一步的 /home/news 匹配 /home 就会卡住不动,这个时候就不会显示有用的东西了!

🍟 6. 传递 params 参数

react-router-params

首先我们需要实现的效果是,点击消息列表,展示出消息的详细内容

这个案例实现的方法有三种,第一种就是传递 params 参数,由于我们所显示的数据都是从数据集中取出来的,因此我们需要有数据的传输给 Detail 组件

我们首先需要将详细内容的数据列表,保存在 DetailData 中,将消息列表保存在 Message 的 state 中。

我们可以通过将数据拼接在路由地址末尾来实现数据的传递

 <Link to=&#123;`/home/message/detail/$&#123;msgObj.id&#125;/$&#123;msgObj.title&#125;`&#125;>&#123;msgObj.title&#125;</Link>

如上,我们将消息列表的 id 和 title 写在了路由地址后面

这里我们需要注意的是:需要采用模板字符串以及 $ 符的方式来进行数据的获取

在注册路由时,我们可以通过 :数据名 来接收数据

<Route path="/home/message/detail/:id/:title" component=&#123;Detail&#125; />

如上,使用了 :id/:title 成功的接收了由 Link 传递过来的 id 和 title 数据

这样我们既成功的实现了路由的跳转,又将需要获取的数据传递给了 Detail 组件

我们在 Detail 组件中打印 this.props 来查看当前接收的数据情况

image-20210906153042353

我们可以发现,我们传递的数据被接收到了对象的 match 属性下的 params 中

因此我们可以在 Detail 组件中获取到又 Message 组件中传递来的 params 数据

并通过 params 数据中的 id 值,在详细内容的数据集中查找出指定 id 的详细内容

const &#123; id, title &#125; = this.props.match.params
const findResult = DetailData.find((detailObj) => &#123;
    return detailObj.id === id
&#125;)

最后渲染数据即可

🍀 7. 传递 search 参数

我们还可以采用传递 search 参数的方法来实现

首先我们先确定数据传输的方式

我们先在 Link 中采用 ? 符号的方式来表示后面的为可用数据

<Link to=&#123;`/home/message/detail/?id=$&#123;msgObj.id&#125;&title=$&#123;msgObj.title&#125;`&#125;>&#123;msgObj.title&#125;</Link>

采用 search 传递的方式,无需在 Route 中再次声明,可以在 Detail 组件中直接获取到

image-20210906155217647

我们可以发现,我们的数据保存在了 location 对象下的 search 中,是一种字符串的形式保存的,我们可以引用一个库来进行转化 querystring

import qs from 'querystring'

这个库是 React 中自带有的,它有两个方法,一个是 parse 一个是 stringify

我们可以采用 parse 方法,将字符串转化为键值对形式的对象

const &#123; search &#125; = this.props.location
const &#123; id, title &#125; = qs.parse(search.slice(1))

这样我们就能成功的获取数据,并进行渲染

tips:无需声明接收

🌷 8. 传递 state 参数

采用传递 state 参数的方法,是我觉得最完美的一种方法,因为它不会将数据携带到地址栏上,采用内部的状态来维护

<Link to=&#123;&#123; pathname: '/home/message/detail', state: &#123; id: msgObj.id, title: msgObj.title &#125; &#125;&#125;>&#123;msgObj.title&#125;</Link>

首先,我们需要在 Link 中注册跳转时,传递一个路由对象,包括一个 跳转地址名,一个 state 数据,这样我们就可以在 Detail 组件中获取到这个传递的 state 数据

注意:采用这种方式传递,无需声明接收

我们可以在 Detail 组件中的 location 对象下的 state 中取出我们所传递的数据

const &#123; id, title &#125; = this.props.location.state

image-20210906160940033

直接使用即可~

解决清除缓存造成报错的问题,我们可以在获取不到数据的时候用空对象来替代,例如,

const &#123; id, title &#125; = this.props.location.state || &#123;&#125;

当获取不到 state 时,则用空对象代替

这里的 state 和状态里的 state 有所不同


非常感谢您的阅读,欢迎提出你的意见,有什么问题欢迎指出,谢谢!🎈

🌮 React 入门学习(十二)– React 路由跳转

1. push 与 replace 模式

默认情况下,开启的是 push 模式,也就是说,每次点击跳转,都会向栈中压入一个新的地址,在点击返回时,可以返回到上一个打开的地址,

react-router-push

就像上图一样,我们每次返回都会返回到上一次点击的地址中

当我们在读消息的时候,有时候我们可能会不喜欢这种繁琐的跳转,我们可以开启 replace 模式,这种模式与 push 模式不同,它会将当前地址替换成点击的地址,也就是替换了新的栈顶

我们只需要在需要开启的链接上加上 replace 即可

<Link replace to=&#123;&#123; pathname: '/home/message/detail', state: &#123; id: msgObj.id, title: msgObj.title &#125; &#125;&#125;>&#123;msgObj.title&#125;</Link>

react-router-replace

2. 编程式路由导航

我们可以采用绑定事件的方式实现路由的跳转,我们在按钮上绑定一个 onClick 事件,当事件触发时,我们执行一个回调 replaceShow

这个函数接收两个参数,用来仿制默认的跳转方式,第一个是点击的 id 第二个是标题

我们在回调中,调用 this.props.location 对象下的 replace 方法

replaceShow = (id, title) => &#123;
  this.props.history.replace(`/home/message/detail/$&#123;id&#125;/$&#123;title&#125;`)
&#125;

同时我们可以借助 this.props.history 身上的 API 实现路由的跳转,例如 gogoBackgoForward

3. withRouter

当我们需要在页面内部添加回退前进等按钮时,由于这些组件我们一般通过一般组件的方式去编写,因此我们会遇到一个问题,无法获得 history 对象,这正是因为我们采用的是一般组件造成的。

image-20210906231051190

只有路由组件才能获取到 history 对象

因此我们需要如何解决这个问题呢

我们可以利用 react-router-dom 对象下的 withRouter 函数来对我们导出的 Header 组件进行包装,这样我们就能获得一个拥有 history 对象的一般组件

我们需要对哪个组件包装就在哪个组件下引入

// Header/index.jsx
import &#123; withRouter &#125; from 'react-router-dom'
// 在最后导出对象时,用 `withRouter` 函数对 index 进行包装
export default withRouter(index);

这样就能让一般组件获得路由组件所特有的 API

4. BrowserRouter 和 HashRouter 的区别

它们的底层实现原理不一样

对于 BrowserRouter 来说它使用的是 React 为它封装的 history API ,这里的 history 和浏览器中的 history 有所不同噢!通过操作这些 API 来实现路由的保存等操作,但是这些 API 是 H5 中提出的,因此不兼容 IE9 以下版本。

对于 HashRouter 而言,它实现的原理是通过 URL 的哈希值,但是这句话我不是很理解,用一个简单的解释就是

我们可以理解为是锚点跳转,因为锚点跳转会保存历史记录,从而让 HashRouter 有了相关的前进后退操作,HashRouter 不会将 # 符号后面的内容请求。兼容性更好!

地址栏的表现形式不一样

  • HashRouter 的路径中包含 # ,例如 localhost:3000/#/demo/test

刷新后路由 state 参数改变

  1. 在BrowserRouter 中,state 保存在history 对象中,刷新不会丢失
  2. HashRouter 则刷新会丢失 state

React 入门学习(十三)– antd 组件库的基本使用

引言

在我们学习 JavaScript 的时候,我们学习了一个 bootstrap 的组件库。可以让我们快速开发,但是我们现在学习了 React ,一种组件化编程方式,很少说会去贴大量的 HTML 代码,再配一下 CSS,JS。我们也有一些现成的组件库可以使用,我们只需要写一个组件标签即可调用。这让我们 React 开发变得十分的快速,方便和整洁。

我们这里学习的是 Ant-design (应该是这样),它有很多的组件供我们使用

image-20210907180731157

按钮,日历,这些都是非常常用的组件,我们一起看看如何使用吧

1. Antd 组件基本使用

使用 Antd 组件非常的简单

引包 —– 暴露 —- 使用

首先我们通过组件库来实现一个简单的按钮

第一步

安装并引入 antd

使用命令下载这个组件库

yarn add antd

在我们需要使用的文件下引入,我这里是在 App.jsx 内引入

import &#123; Button &#125; from 'antd'

在引入的同时,暴露出要使用的组件名 Button

推荐去官方文档查看,都会有代码解释

image-20210907181354552

现在我们可以在 App 中使用 Button 组件

<div>
    App..
    <Button type="primary">Primary Button</Button>
    <Button>Default Button</Button>
    <Button type="dashed">Dashed Button</Button>
    <br />
    <Button type="text">Text Button</Button>
    <Button type="link">Link Button</Button>
</div>

我这里使用了几种按钮

但是就这样你会发现按钮少了样式

我们还需要引入 antd 的 CSS 文件

@import '/node_modules/antd/dist/antd.less';

可以在 node_modules 文件中的 antd 目录下的 dist 文件夹中找到相应的样式文件,引入即可

image-20210907181854774

即可成功引入 antd 组件

2. 自定义主题颜色

由于这些组件采用的颜色,都是支付宝蓝,有时候我们不想要这样的颜色,想要用其他的配色,这当然是可以实现的,我们需要引用一些库和更改一些配置文件来实现

在视频中,老师讲解的是 3.几 版本中的实现方法,这种方法需要去暴露 React 中的配置文件,这种操作是不可返回的,一旦暴露就不可回收。我觉得这不是一个好方法~

antd 最新版中,引入了 craco 库,我们可以使用 craco 来实现自定义的效果

首先我们需要安装 craco

yarn add @craco/craco

同时我们需要更改 package.json 中的启动文件

"scripts": &#123;
  "start": "craco start",
  "build": "craco build",
  "test": "craco test",
  "eject": "react-scripts eject"
&#125;,

更改成 craco 执行

接下来我们需要在根目录下新建一个 craco.config.js 文件,用于配置自定义内容

const CracoLessPlugin = require('craco-less');

module.exports = &#123;
  plugins: [
    &#123;
      plugin: CracoLessPlugin,
      options: &#123;
        lessLoaderOptions: &#123;
          lessOptions: &#123;
            modifyVars: &#123; '@primary-color': 'skyblue' &#125;,
            javascriptEnabled: true,
          &#125;,
        &#125;,
      &#125;,
    &#125;,
  ],
&#125;;

其实它就是用来操作 less 文件的全局颜色

简单的说,antd 组件是采用 less 编写的,我们需要通过重新配置的方式去更改它的值

同时我们需要将我们先前的 App.css 文件更改为 App.less 文件,在当中引入我们的 less 文件

@import '/node_modules/antd/dist/antd.less';

注意一定要添加分号结尾,这是一个非常容易犯的错误

image-20210907200116517

可见,我们成功的将主题色修改成了红色

antd ui组件库就记这么多,还有样式的按需引入没有记录,不太喜好暴露 React 配置文件…