React中css写法、Redux、Route、初识hooks——前端之React之三【Day91-Day104】

166 阅读18分钟

挑战坚持学习1024天——前端之React

js基础部分可到我文章专栏去看 ---点击这里

Day91-Day97【2022年11月14日-27日】

学习重点React中css写法、Redux、Route、初识hooks

1.React中编写CSS的方式有哪些?各自有什么优缺点?

1.1内联样式

内联样式是官方推荐的一种css样式的写法:style 接受一个采用小驼峰命名属性的 JavaScript 对象,,而不是 CSS 字符串;并且可以引用state中的状态来设置相关的样式;

内联样式的优点:

1.内联样式, 样式之间不会有冲突

2.可以动态获取当前state中的状态

内联样式的缺点:

1.写法上都需要使用驼峰标识

2.某些样式没有提示

3.大量的样式, 代码混乱

4.某些样式无法编写(比如伪类/伪元素)

普通的css我们通常会编写到一个单独的文件,之后再进行引入。这样的编写方式和普通的网页开发中编写方式是一致的:如果我们按照普通的网页标准去编写,那么也不会有太大的问题;但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;但是普通的css都属于全局的css,样式之间会相互影响; 这种编写方式最大的问题是样式之间会相互层叠掉;

1.2.css modules

css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。 如果在其他项目中使用它,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等。 React的脚手架已经内置了css modules的配置:.css/.less/.scss 等样式文件都需要修改成 .module.css/.module.less/.module.scss 等;之后就可以引用并且进行使用了;css modules确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方案。本质上是让class有唯一变量名。构建工具会将类名style.title编译成一个哈希字符串。

但是这种方案也有自己的缺陷:

引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;

所有的className都必须使用{style.className} 的形式来编写;

不方便动态来修改某些样式,依然需要使用内联样式的方式;

如果你觉得上面的缺陷还算OK,那么你在开发中完全可以选择使用css modules来编写,并且也是在React中很受欢迎的一种方式。

1.3 CSS in JS

官方文档也有提到过CSS in JS这种方案:“CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义;注意此功能并不是 React 的一部分,而是由第三方库提供;React 对样式如何定义并没有明确态度; 在传统的前端开发中,我们通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离。 但是在前面的学习中,我们就提到过,React的思想中认为逻辑本身和UI是无法分离的,所以才会有了JSX的语法。 样式呢?样式也是属于UI的一部分; 事实上CSS-in-JS的模式就是一种将样式(CSS)也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态;所以React有被人称之为 All in JS;当然,这种开发的方式也受到了很多的批评:Stop using CSS in JavaScript for web developmenthttps://hackernoon.com/stop-using-css-in-javascript-for-web-development-fa32fb873dcc批评声音虽然有,但是在我们看来很多优秀的CSS-in-JS的库依然非常强大、方便:CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修 改状态等等;虽然CSS预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点;所以,目前可以说CSS-in-JS是React编写CSS最为受欢迎的一种解决方案; 目前比较流行的CSS-in-JS的库有哪些呢?

styled-components

emotion

glamorous

目前可以说styled-components依然是社区最流行的CSS-in-JS库,所以我们以styled-components的讲解为主;

2.什么是redux?redux的核心思想是什么?核心的原则有哪些?(面试)

Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态管理, 核心思想有三

Redux的核心理念 - Store

Redux的核心理念非常简单。 比如我们有一个朋友列表需要管理:如果我们没有定义统一的规范来操作这段数据,那么整个数据的变化就是无法跟踪的;比如页面的某处通过products.push的方式增加了一条数据;比如另一个页面通过products[0].age = 25修改了一条数据;整个应用程序错综复杂,当出现bug时,很难跟踪到底哪里发生的变化。

Redux的核心理念 - action

Redux要求我们通过action来更新数据:所有数据的变化,必须通过派发(dispatch)action来更新;action是一个普通的JavaScript对象,用来描述这次更新的type和content;比如下面就是几个更新friends的action:强制使用action的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可跟追、可预测的;当然,目前我们的action是固定的对象;真实应用中,我们会通过函数来定义,返回一个action。

Redux的核心理念 - reducer

但是如何将state和action联系在一起呢?答案就是reducerreducer是一个纯函数;reducer做的事情就是将传入的state和action结合起来生成一个新的state;

Redux的三大原则

单一数据源

整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中:

Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护;

单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改;

State是只读的

唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State:

样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state;

这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;

使用纯函数来执行修改

通过reducer将 旧state和 actions联系在一起,并且返回一个新的State:

随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分;

但是所有的reducer都应该是纯函数,不能产生任何的副作用。

3.redux中如何进行异步的操作?和同步操作有什么区别?

  • 通过中间件

    • redux-thunk
    • redux-saga
  • redux中的同步操作

    • 执行了dispatch函数之后,对应的reducer函数收到action对象后立即得到执行,reducer执行完了之后,state立即就改变了,此时store.getState函数,取到的是最新的值
  • redux的异步操作

    • 原则上redux并没有提供异步action的处理方案,异步action需要依赖第三方的中间件解决
    • dispatch一个异步函数,目标state不会立即响应,而是要看异步函数内部的逻辑,来决定state什么时候响应

4.redux中如何进行reducer的拆分?拆分的原理和本质是什么?

  • 主要利用模块化的思想,将不同的数据拆分到不同的模块

  • 每一模块都有自己的目录结构

    • reducer ---> 接受action对象,返回最新的state
    • constants ---> 定义常量数据
    • actoinCreator ---> 定义创建action对象的函数
    • index ---> 导出reducer
  • store中的index文件

    • 合并reducer,导出store实例

5.什么是Redux Toolkit?核心API有哪些?并且说出他们的作用。

  • configureStore

    • 包装createStore,同时提供简化的配置选项和良好的默认值

    • 自动组合单独的slice reducer

    • 可以添加任何中间件

      • 默认包含redux-thunk,并启用Redux-DevTools调试工具
  • createSlice

    • 接受一个具有render函数的对象
    • 可以配置切片名称
    • 初始状态值
    • 自动 生成切片,并带有相应的actions
  • createAsyncThunk

    • 接受一个动作类型字符和一个返回承诺的函数
    • 生成一个pending/fulfilled/rejected基于该承诺分配动作类型的Thunk

6.总结Redux的使用步骤,包括原始Redux用法和RTK用法。(重要)

  • 原始的Redux的使用步骤

    • 先从react-redux中导入Providre包裹根组件

    • 将导出的store绑定到Provider组件的store属性中

    • 创建store,目录结构

      • actionCreator ---> 创建action对象
      • constant ---> 定义常量数据
      • reducer ---> 处理action对象,返回最新的state
      • index ---> 人口文件,创建store,使用中间件
    • 组件中的使用方式

      • 定义函数 ---> mapStateToProps ---> 将store中的数据映射要组件的props中
      • 定义函数 ---> mapDispatchToProps ---> 将dispatch的操作映射到props中
      • 从react-redux中导入高阶组件对要导出的组件进行包裹,并把定义的函数传入connect函数
      • 组件触发相应的事件,dispatch相应的对象,store中的数据改变,组件重新渲染
  • RTK用法

    • 先从react-redux中导入Providre包裹根组件

    • 将导出的store绑定到Provider组件的store属性中

    • 创建store,目录结构:

      • index.js ---> 入口文件 ---> 创建和配置store ---> 主要是合并render

      • features ---> 要管理的数据模块

        • 使用createSliceAPI创建一个slice对象
        • name ---> 配置slice对象的名称
        • initialState ---> 定义初始值
        • reducer ---> 定义reduce函数的对象
        • 导出slice对象的actions---> 组件中使用或者自己内部使用,
        • 导出slice对象的reducer---> index文件合并reducer

7. React Router6的基本创建过程是什么?进行步骤总结。

  • 安装react-router-dom -- npm install react-router-dom

  • 选择路由模式 BrowserRouter使用history模式 / HashRouter使用hash模式

    <HashRouter>
        <App />
    </HashRouter>
    
  • 通过Routes包裹所有的Route, 在其中匹配路由

  • Route用于路径的匹配

    • path属性:用于设置匹配到的路径
    • element属性:设置匹配到路径后,渲染的组件 -- Router5.x使用的是component属性
  • 路由跳转 -- Link组件 / NavLink组件

    • to属性 -- 用于设置跳转到的路径
  • 路由重定向 -- Navigate

  • Not Found页面配置 -- path="*"

    <Link to="/home">首页</Link>
    <Link to="/about">关于</Link>
    <Routes>
        <Route path="/" element={<Navigate to="/home" />} />
        {/**Home页面*/}
        <Route path="/home" element={<Home />}>
          <Route path="/home" element={<Navigate to="/home/homebanner" />} />
          <Route path="/home/homebanner" element={<HomeBanner />} />
          <Route path="/home/homerecommend" element={<HomeRecommend />} />
        </Route>
        <Route path="/about" element={<About />}></Route>
        <Route path="*" element={<NotFount />}></Route>
    </Routes>
    

8.React Router6的路由嵌套如何配置?Outlet的作用是什么?

// Home 页面
<Link to="/home/homebanner">轮播</Link>
<Link to="/home/homerecommend">推荐</Link><Outlet/>
  • Outlet -- Outlet组件用于在父路由元素中作为子路由的占位元素, 类似于Vue中的 router-view

9.React Router6如何传递参数?如何在组件中获取参数?

  • 路由参数传递有两种方式: 1. 动态路由的方式 2. search传递参数

  • 动态路由

    <Link to="/user/9978">用户</Link>
    <Route path="/user/:id" element={<User />}></Route>
    
    // 获取动态路由参数 -- 需要通过 useParams 只能在函数式组件中使用
    import { useParams } from "react-router-dom";
    export function User() {
      const params = useParams()
      return (
        <div>
          <h4>User Page</h4>
          <h4>id: {params.id}</h4>
        </div>
      )
    }
    
  • search传递参数

    <Link to="/user?name=大大怪将军&age=19"">用户</Link>
    

10.类组件无法直接使用navigate、location等参数,应该如何进行操作?

import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
​
function withRouter(WrapperComponent) {
  return function(props){
    // 1.导航
    const navigate = useNavigate()
​
    // 2.动态路由的参数: /detail/:id
    const params = useParams()
​
    // 3.查询字符串的参数: /user?name=why&age=18
    const location = useLocation();
    const [searchParams] = useSearchParams();
    const query = Object.fromEntries(searchParams);
​
    const router = {navigate, params, location, query}
​
    return <WrapperComponent {...props} router={router} />
  }
}
export default withRouter
import React, { PureComponent } from 'react'
import withRouter from "../hoc/with_router"export class About extends PureComponent {
  navigateTo(path) {
    const { navigate } = this.props.router
    navigate(path)
  }
  render() {
    const { query } = this.props.router
    return (
      <div>
        <h3>About Page</h3>
        <h4>name: {query.name}-age: {query.age}</h4>
      </div>
    )
  }
}
export default withRouter(About)

11. React Router6如何进行路由配置?如何配置路由的懒加载?

// 在单独的router/index.js文件中

// 路由懒加载/按需加载/异步加载 ? 
// 这样暂时不会显示,因为是异步的要单独下载,需要加载一个loading动画React提供的Suspense组件
const Order = React.lazy(() => import("../page/Order"));
const User = React.lazy(() => import("../page/User"));

const router = [
    {
        path: '/',
        element: <Navigate to="/home" />,
    },
    {
        path: "/home",
        element: <Home />,
        children: []
    }
]
export default router
<HashRouter>
  <Suspense fallback={<h4>Loading~~~~</h4>}>
    <App />
  </Suspense>
</HashRouter>

12.什么是Hooks?和传统的函数式组件有什么区别?和类组件有什么区别?(面试)

  • Hook指的类似于useState、useEffect这样的函数, Hooks是对这类函数的统称

  • 首先需要了解函数式组件与类组件的优缺点

  • 类组件可以定义自己的state,并且可以保存自己内部的状态

    • 函数式组件不能定义自己的状态的, 因为函数每次调用都会产生新的临时变量
  • 类组件有自己的生命周期 -- 而函数式组件没有

  • 类组件在状态改变是会重新执行render函数

    • 函数式组件时不会重新渲染的, 如果重新渲染, 整个函数会被重新执行, 相应的状态也会被重新赋值
  • 同时类组件也有自己的缺点

    • 随着业务的增多,类组件会变得越来越复杂
    • 复用其中的状态也会很艰难,有时需要通过一些高阶组件
  • Hooks可以让我们在不编写class的情况下使用state以及其他的React特性

    • Hook只能在函数组件中使用,不能在类组件
    • 通过Hook可以在函数式组件中 定义自己的状态 完成类似于class组件中的生命周期功能
    // 类组件实现计数器
    import React, { PureComponent } from 'react'
    
    export class CounterClass extends PureComponent {
      constructor() {
        super()
        this.state = {
          counter: 100
        }
      }
      subCounter() {
        this.setState({counter: this.state.counter - 1})
      }
      addCounter() {
        this.setState({counter: this.state.counter + 1})
      }
      render() {
        const { counter } = this.state
        return (
          <div>
            类组件实现计数器
            <h2>counter: {counter}</h2>
            <button onClick={() => this.subCounter(1)}>-1</button>
            <button onClick={() => this.addCounter(1)}>+1</button>
          </div>
        )
      }
    }
    
    export default CounterClass
    
    // Hooks实现计数器
    import React, { memo, useState } from 'react'
    const CounterHooks = memo(() => {
      let [counter, setCounter] = useState(100)
      return (
        <div>
          Hooks
          <h2>counter: {counter}</h2>
          <button onClick={e => setCounter(counter - 1)}>-1</button>
          <button onClick={e => setCounter(counter + 1)}>+1</button>
        </div>
      )
    })
    
    export default CounterHooks
    

11.14-11.27知识积累

可以约定对于全局样式或者是公共组件样式,可以用 .css 文件 ,不需要做 CSS Modules 处理,这样就不需要写 :global 等繁琐语法。

对于项目中开发的页面和业务组件,统一用 scss 或者 less 等做 CSS Module,也就是 css 全局样式 + less / scss CSS Modules 方案。这样就会让 React 项目更加灵活的处理 CSS 模块化。我写一个 demo 如下

或者使用css in js

或者只使用less 在每个样式外面都包裹一个独立的名字,这样不会样式污染

◼ 函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
 在react开发中纯函数是被多次提及的;
 比如react中组件就被要求像是一个纯函数(为什么是像,因为还有class组件),redux中有一个reducer的概念,也是要求
必须是一个纯函数;
 所以掌握纯函数对于理解很多框架的设计是非常有帮助的;
◼ 纯函数的维基百科定义:
 在程序设计中,若一个函数符合以下条件,那么这个函数被称为纯函数:
 此函数在相同的输入值时,需产生相同的输出。
 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
◼ 当然上面的定义会过于的晦涩,所以我简单总结一下:
 确定的输入,一定会产生确定的输出;
 函数在执行过程中,不能产生副作用;

对象不能添加相同的键名,如果有相同的则会以最后一个键为准,而数组可以

store->subscript->action->dispatch->reducer(纯函数)

react route 传值

git合并分支操作:
合并分支前先提交代码,否则会把未提交的代码合并过去或者合并失败不让合并

具体操作如下:
当前分支dev 合并到main
1.提交代码 :git push
2.切换分支: git chenkout main
3.合并dev到main : git merge dev

redux-thunk中间件,本来dispatch只能派发对象,用这个可以派发函数,本质上是做了一个拦截,如果是派发的函数,就执行这个函数,并且传入一定的参数state等

react状态管理:
组件内部可维护的状态由组件自己来维护 按钮 input等的状态
大部分需要共享的状态redux来维护
服务器的状态由redux来管理比较好

HashRouter(hash模式location.hash、location.replace;和vue router的hash模式实现一致)
BrowserRouter(基于history模式:页面跳转原理是使用了HTML5为浏览器全局的history对象新增了两个API,包括 history.pushState、history.replaceState;和vue router的history模式实现一致)

路由的核心:映射配置

git commit 提交规范

fix(登录/hnd1204):添加登录    括号中写一个即可

build 编译相关的修改,如版本发布、对半项目构建或者依赖改动
chore 其他修改,比如改变构建流程、或增加依赖库、工具等
ci 持续集成修改
docs 文档修改
feat 新特性、新功能
fix 修改bug
perf 优化相关 比如性能、体验
refactor 代码重构
revert 回滚到上一个版本
style 代码格式修改,注意不是css格式
test 测试用例修改

用的较多 feat fix perf

打包空间分析工具:
webpack-bundle-analyzer

当你明白了所有的焦虑都来自于认知的偏差,你对一个困扰你的事物从多角度看待,你就不会焦虑。

async与普通函数的区别:
一、async函数单独使用的时候在没有遇到await之前都是同步执行的这点跟普通函数一样
二、async可以搭配await一起使用 async 它就是 Generator 函数的语法糖。
三、async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。
四、普通函数抛出异常,终止后面代码执行async异步函数可以捕获错误处理,不会终止后续代码执行

所以async函数执行比起普通函数是一个增强版

好的代码不是教出来的,更多是师傅领进门。这个东西就像武侠小说,真正的高手不用你出手,光是走几步,就看得出来了。
具体步骤如下:
明确编程目标---> github看看做的最好的是怎么做的 ---> 代码一点点拆分、阅读、分析,看看有什么可以学习、借鉴、模仿,甚至蒙住看自己会怎么写 ---> 自己动手做,学习,比较,学习。

精进

11.14精进
夫唯不争,故天下莫能与之争。 --《道德经》唯有不争的处事态度不争名、争利,不逞强、不显摆,天下才会没有人能与之抗衡。
11.15精进
不要用战术上的勤奋来掩盖战略上的懒惰。
11.16精进
要用辩证的眼光去看待事物,事物=是在不断发展变化的,不同背景下对同一个事物可做出不同的判断。
11.19精进
将欲取之,必先与之。要想让一个人为你卖力,你就要先搞清楚他想要什么,并让他觉得你可以帮他实现需求。
11.22精进
粗心原因可归结为以下几方面一、对事情不够重视未真的经过大脑 二、与生活生活习惯有一定关系三、心态
11.23精进
所有的焦虑都来自于认知的偏差,你对一个困扰你的事物从多角度看待,你就不会焦虑。
11.25精进
试错越晚,成本越高。不论是工作,还是爱情。尽早尝试,摸清自己的能力边界,可以避免耗费大量心力。
11.27精进
好的程序员不是教出来的,更多是师傅领进门。这个东西就像武侠小说,真正的高手不用你出手,光是走几步,就看得出来了。
具体步骤如下:
明确编程目标---> github看看做的最好的是怎么做的 ---> 代码一点点拆分、阅读、分析,看看有什么可以学习、借鉴、模仿,甚至蒙住看自己会怎么写 ---> 自己动手做,学习,比较,学习。
11.28精进
所谓人生,归根到底,就是“一瞬间、一瞬间持续的积累”,如此而已。
11.29精进
”努力想要得到什么东西,其实只要沉着冷静、实事求是,就可以轻易地、神不知鬼不觉地达到目的“卡夫卡《城堡》

参考资料

  • Coderwhy学习资料
  • React官网
  • 解锁前端面试体系核心攻略
  • 鲨鱼哥整理面试资料
  • 其他参考资料 结语

志同道合的小伙伴可以加我,一起交流进步,我们坚持每日精进(互相监督思考学习,如果坚持不下来我可以监督你)。我们一起努力鸭! ——>点击这里

备注

按照时间顺序倒叙排列,完结后按时间顺序正序排列方便查看知识点,工作日更新。