挑战坚持学习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官网
- 解锁前端面试体系核心攻略
- 鲨鱼哥整理面试资料
- 其他参考资料 结语
志同道合的小伙伴可以加我,一起交流进步,我们坚持每日精进(互相监督思考学习,如果坚持不下来我可以监督你)。我们一起努力鸭! ——>点击这里
备注
按照时间顺序倒叙排列,完结后按时间顺序正序排列方便查看知识点,工作日更新。