一、JSX
1. JSX是什么?
jsx是一种JavaScript的语法扩展,也在很多地方称为JavaScript XML,类似XML语法,能够描述我们的UI界面,能与 JavaScript 融合在一起
2. JSX的使用
2.1 常规规范
2.3 jsx嵌入变量作为子元素
- 当变量是Number、String、Array类型时,可以直接显示
- 当变量是null、undefined、Boolean类型时,内容为空
- 显示null、undefined、Boolean需要将其转换为字符串
- 转换方式:toString、字符串拼接、String()
-
2.4 jsx嵌入表达式
运算表达式
- 三元运算符
-
2.5 jsx绑定属性
基本绑定:title、href、src等
- 绑定class属性:最好使用className
- 绑定style属性:绑定对象类型
3. 事件绑定
3.1 this绑定方案
- 方案一:通过bind显示绑定
- 方案二:使用ES6 class fields 语法
-
3.2 参数绑定
情况一:获取event对象
- 情况二:获取更多参数,传入一个箭头函数以及主动执行的事件函数,并传入其它相关的参数
4. 条件渲染
- 根据条件给变量赋值不同的内容
- 三元运算符
&& 逻辑与运算
使用 map 高阶函数
- 过滤一些内容:使用 filter 函数
- 截取数组中的一部分:slice 函数
5.1 列表中的Key
6. JSX本质
实际上,jsx只是 React.createElement(component, props, …children) 函数的语法糖
参数一:type
- 当前 ReactElement 的类型
- 标签元素,如 div 则为“div”
- 组件元素,直接使用组件元素的名称
参数二:config
- jsx中的属性都在config中以对象的属性和值的形式存储
参数三:children
- 存放在标签中的内容,以children数组的方式存储
二、React脚手架
1. 脚手架是什么?
传统的脚手架指的是建筑学的一种结构,在搭建楼房、建筑物时,临时搭建出来的一个框架
编程中的脚手架(Scaffold),是一种工具,帮助我们快速生成项目的工程化结构
- 帮助我们快速搭建项目
- 在模板的基础上进行项目开发或进行一些简单的配置修改
- 可以保证项目的基本机构一致性,方便后期维护
脚手架让项目从搭建到开发,再到部署,整个流程变得快速和便捷
现流行的脚手架
- PWA全称 progressive web app,即渐进式web应用
- 一个PWA应用首先是一个网页,可以通过web技术编写一个网页应用(类似于手机上的app)
- APP Manifest 和 Service Worker是用来实现PWA的安装和离线缓存的功能
PWA解决了哪些问题?
- 可以将网页应用添加至主屏幕
- 实现了离线缓存功能
- 实现了消息推送
三、React组件开发(一)
1. 什么是组件化开发?
将一个页面拆分成一个个的功能块,每个功能块完成属于自己的这部分独立的功能,那么之后整个页面的管理和维护就变得很容易了
组件开发核心思想:
- 将一个页面分成很多个组件
- 每个组件实现页面的一个功能块
- 每个组件再进行细分
- 组件本身再进行复用
2. React组件类型
- 根据组件的定义方式
- 函数组件(Function Component)
- 类组件(Class Component)
- 根据组件内部是否有状态需要维护
- 无状态组件(Stateless Component)
- 有状态组件(Stateful Component)
- 根据组件的不同职责
- 展示型组件(Presentational Component)
- 容器型组件(Container Component)
函数组件、无状态组件、展示型组件主要关注UI的展示
类组件、有状态组件、容器型组件主要关注数据逻辑
2.1 类组件
定义要求
- 组件名称大写字符开头
- 必须继承 React.Component
- 必须实现 render 函数
class定义一个组件
- constructor是可选的,可以在其中初始化一些数据
- this.state 维护的是组件内部的数据
- render() 方法必须实现
render函数的返回值
当 render 被调用时,它会检测 this.props 和 this.state 的变化并返回以下类型之一
- React元素:JSX元素(本质 React.createElement)
- 数组或fragments:数组被遍历
- Portals:可以渲染子节点到不同的 DOM 子树中
- 字符串或数值类型:会在 DOM 中被渲染为文本节点
- 布尔类型、null、undefined:都不会被显示
2.2 函数式组件
函数式组件是使用 function 来进行定义的函数,只是这个函数返回值与类组件中 render 函数返回的值一致 ```jsx //函数式组件 function App() { returnapp component
}
export default App
函数式组件的特点
1. 没有生命周期函数
2. this关键字不能指向组件实例(因为没有组件实例)
3. 没有内部状态(state)
<a name="mjet7"></a>
## 3. 生命周期
生命周期:事物从创建到销毁的过程
<a name="AyhuW"></a>
### 3.1 生命周期和生命周期函数
- 生命周期:是一个抽象的概念,描述生命周期的整个过程
- 挂载阶段(mount),组件第一次在DOM树中被渲染的过程
- 更新阶段(update),组件状态发生变化,重新更新渲染的过程
- 卸载过程(unmount),组件从DOM树中被移除的过程
- 生命周期函数(常用):React告诉我们当前处于哪个阶段,会在组件内部实现某些函数进行回调
- Constructor(可选)
- 初始化数据(this.state)
- 为事件绑定实例(this)
- componentDidMount函数:组件已挂载到DOM上时回调
- 依赖于 DOM 的操作可以在此执行
- 在此发送网络请求
- 在此添加一些订阅(在componentWillUnmount中取消订阅)
- componentDidUpdate函数:组件发生了更新时回调(首次渲染不会执行)
- 组件更新时,对 DOM 进行操作
- 组件更新后,对 props 进行比较,然后进行相应的网络请求
- componentWillUnmount函数:组件即将被移除时回调
- 执行一些必要的清理操作
- 清除一些 timer、取消网络请求、清除创建的订阅等
![image.png](https://cdn.nlark.com/yuque/0/2022/png/26749762/1666974362989-31e0d12b-dbcf-40bb-9c7c-d39fe4beac5b.png#averageHue=%23faf9f7&clientId=ua9121097-cd87-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=347&id=ub4404ff2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=434&originWidth=1250&originalType=binary&ratio=1&rotation=0&showTitle=false&size=43370&status=done&style=none&taskId=u48d69b84-b97a-4e71-9382-79c16cb2d5d&title=&width=1000)
- 生命周期函数(不常用)
- getDerivedStateFromProps:state 的值在任何时候都依赖于 props 时使用,该方法返回一个对象来更新state
- getSnapshotBeforeUpdate:在 React 更新 DOM 之前回调的一个函数,可以获取 DOM 更新前的一些信息(比如滚动位置)
- shouldComponentUpdate:DOM 更新时控制 React 是否调用 render 函数,在做性能优化时会用到
![image.png](https://cdn.nlark.com/yuque/0/2022/png/26749762/1666974337593-b46299b8-5c75-42d3-b20f-4e3c1455b84c.png#averageHue=%23faf9f7&clientId=ua9121097-cd87-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=395&id=u3e39bdbb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=667&originWidth=1293&originalType=binary&ratio=1&rotation=0&showTitle=false&size=57850&status=done&style=none&taskId=ubd3fc953-1471-4ec1-8a5d-4225dbf891f&title=&width=765)
<a name="qwch3"></a>
## 4. 组件间的通信
<a name="YCnv4"></a>
### 4.1 父传子
- 父组件通过 **属性=值** 的形式传递给子组件
- 子组件通过 **props** 参数获取父组件传递的数据
:::tips
对于 props
1. 校验 props:Component.propTypes = {}
- 可使用 propTypes 对 props 进行类型校验
- 若使用Flow或TypeScript,则可以直接进行类型验证
2. props默认值:Component.defaultProps = {}
:::
<a name="fv9CF"></a>
### 4.2 子传父
父组件传递给子组件一个回调函数,子组件通过 props 使用该回调函数,子组件利用回调函数给父组件传递参数
<a name="F08GH"></a>
### 4.3 Context共享数据
Context用于层层传递的数据
- Context 提供了一种在组件之间共享此类值的方式,不必显式地通过组件树的逐层传递 props
- Context 目的是为了共享那些对于一个组件树而言是“全局“的数据
Context实现步骤(类组件)
> 1. 创建一个Context:const themeContext = React.createContext()
> 2. 使用`<ThemeContext.Provider value={{name: 'coder'}}><Home/></ThemeContext.Provider>`包裹需要传递的子组件,并通过value属性设置需要共享的数据
> 3. 在Home子组件(需要引入创建的Context)中显式地设置contentType值为themeContext:`Home.contentType = themeContext`
> 4. 在子孙组件中通过this.context获取共享的数据,并使用
Context实现步骤(函数组件)
> 1. 创建一个Context:const themeContext = React.createContext()
> 2. 在子孙组件中(需要引入创建的Context)使用ThemeContext .Consumer的方式获取共享数据并使用
![image.png](https://cdn.nlark.com/yuque/0/2022/png/26749762/1667058531933-ed126fe5-6e1b-4454-a7dd-9154a32d816b.png#averageHue=%23292c34&clientId=u93045b1a-9196-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=417&id=u663f064a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=398&originWidth=777&originalType=binary&ratio=1&rotation=0&showTitle=false&size=130263&status=done&style=none&taskId=u2386e808-b486-4e4b-b990-e0d1fb6c5f1&title=&width=813.6000366210938)
<a name="b9y3o"></a>
### 4.4 Event Bus
使用hy-event-store或events第三方库
<a name="ctqT1"></a>
## 5. React中的插槽
React中其实没有插槽的概念,因为React很灵活,可以将自己需要的元素传入即可<br />使用两种方案实现插槽
<a name="zJcA1"></a>
### 5.1 children实现
使用组件 children 子元素的方式:每个组件都可以获取到 props.children(包含组件开始标签和结束标签之间的内容)
- 子元素为一个元素时,则 props.children = ReactElement
- 子元素有多个元素时,则 props.children = [ReactElement1, ReactElement2, ReactElement3]
- 缺点:通过索引值获取传入的元素很容易出错,不能精确获取传入的元素原生
![image.png](https://cdn.nlark.com/yuque/0/2022/png/26749762/1667052172001-1efbd266-90a8-441e-bcb0-7ca4b8e3dde2.png#averageHue=%23282d36&clientId=u93045b1a-9196-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=424&id=u8301432f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=530&originWidth=970&originalType=binary&ratio=1&rotation=0&showTitle=false&size=135683&status=done&style=none&taskId=uff8a595d-0d20-419a-b9e3-7e722b17ff2&title=&width=776)
<a name="HDotL"></a>
### 5.2 props实现(推荐)
使用 props 实现:通过具体的属性名,可以在传入和获取时更加精确
- 模拟 Vue 作用域插槽,父组件传入一个回调函数,子组件调用回调并传入参数,在父组件那里可以获取子组件的参数并自定义元素类型
![image.png](https://cdn.nlark.com/yuque/0/2022/png/26749762/1667055629859-cf3c6598-e92a-4d88-9223-e0e176369bd0.png#averageHue=%23292d36&clientId=u93045b1a-9196-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=341&id=ue8bbe80a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=502&originWidth=1136&originalType=binary&ratio=1&rotation=0&showTitle=false&size=122514&status=done&style=none&taskId=uf2c27b91-6700-4dff-8324-02bc5562afe&title=&width=772)
<a name="sz3z8"></a>
## 6. 解析setState
<a name="NHzW9"></a>
### 6.1 Vue和React渲染流程的对比
<a name="MVzkk"></a>
#### 6.1.1 Vue对数据管理和界面渲染的流程
对template进行词法分析、语法分析生成AST再生成JavaScriptAST最后生成render函数,相当于`<div></div> -> h("div", {}, "")`,并且在解析的过程中会对v-for、v-model等指令进行处理,并进行数据劫持(Vue2使用Object.defineProperty、Vue3使用proxy)来监听数据的变化,当数据发生改变的时候会触发setter,执行相应的依赖,然后主动地调用render()进行数据更新
<a name="Pjc9v"></a>
#### 6.1.2 React对数据管理和界面渲染的流程
React中没有像Vue那样对数据进行劫持,React中所有对数据的操作都是手动处理的,React中render函数是交给我们手动执行的,render函数返回的对象相当于`<div></div> -> React.createElement("div",{}, "")`,在类组件中我们通过this.state手动添加数据,通过this.setState手动去修改数据,然后React会自动调用render函数重新渲染,当修改的数据和原数据相同时,React也会调用render函数重新渲染,这样就会造成不必要的性能浪费,所以可以通过`shouldComponentUpdate()回调函数`去告知React当数据更新时需不需要调用render函数重新渲染;而PureComponent就是帮助我们去做这种优化的(当修改前和后相同时不执行render函数)
<a name="CWDLk"></a>
### 6.2 setState三种用法
内部原理:this.setState会创建一个新的对象newState,内部通过Object.assign(this.state, newState)进行合并
<a name="C3EOL"></a>
#### 6.2.1 基本用法
> this.setState({message: 'aaa'})
<a name="EvmRG"></a>
#### 6.2.2 可以传入一个回调函数
```jsx
this.setState((state, props) => {
// 优点:
// 1.可以编写一些对新state处理逻辑
// 2.可以获取之前的state和props值
return {
message: 'hello world'
}
})
优点:
- 可以编写一些对新state处理逻辑
- 可以获取之前的state和props值
6.2.3 setState是一个异步调用
setState执行之后不会立马合并数据,如果希望调用setState后能够立马获取到更新之后的数据,可以利用setState的第二个参数:传入一个callback,在callback中可以获取到最新的数据
⭐为什么setState设计为异步?this.setState({message: 'aaa'}, () => {
console.log(this.state.message) // 最新的数据:message: 'aaa'
})
- setState设计为异步能显著的提升性能
- 如果每次调用 setState 都进行一次更新,那么意味着 render 函数会被频繁触发调用,界面重新渲染,效率很低
- 设计为异步能够获取到多个更新,并批量处理更新
- 将多个更新操作放入一个队列当中,当执行render时,将队列中的state拿出来依次合并,进行一次render即可
setState设计为异步能保持state和props同步
只有在 React 事件中才会进行批处理(异步)
在Promise回调、setTimeout回调、原生DOM事件回调中 setState 是同步的
6.3.2 在 React18之后
默认所有的操作都进行批处理(异步处理),能够获得更高的性能
如果要使得 setState 同步处理,那么就需要使用 react-dom 包中的 flushSync函数import { flushSync } from 'react-dom'
setTimeout(() => {
flushSync(() => {
// 该回调下的所有setState会进行批处理
this.setState({ message: ''})
})
console.log(this.state.message) // 这里会同步地拿到最新的数据
}, 0)
四、React组件开发(二)
1. React更新机制
React 在 props 或者 state 发生改变的时候,会调用 React 的 render 方法,创建一颗不同的树,React基于这两棵不同的树之间的差异来判断如何有效的更新UI(diff算法)
React的diff算法:同层及诶单之间相互比较,不会跨节点比较
- 不同类型的结点,产生不同的树结构
-
2. render函数的优化
2.1 shouldComponentUpdate(nextPops, nextState)
对前后props/state做一个浅层比较(引用不同就返回true)
- 函数参数
- nextProps:更新后的props属性
- nextState:更新后的state属性
- 返回值:返回一个Boolean类型
- true:需要调用render函数(默认)
- false:不需要调用render函数
当state或props的数据发生变化时,shouldComponentUpdate函数能够控制render函数是否重新执行,当state或props的值发生改变的时候,再执行render函数,否则不执行不重新渲染,那么就可以提高性能
shouldComponentUpdate(nextPops, nextState) {
if(this.state.message !== nextState.message) {
return true // 会执行render函数
}
return false // 不会执行render函数
}
2.2 PureComponent(类组件)
PureComponent是React帮助我们实现好的一个类,它内部做的事情就是我们使用shouldComponentUpdate时做的事情,根据props或state中的数据是否发生改变,来决定是否重新执行render函数
// 使用类组件时,可以继承PureComponent,进行性能优化
import { PureComponent } from 'react'
class App extends PureComponent {
...
}
2.3 memo(函数式组件)
在函数式组件中,使用React提供的memo函数对组件进行包裹,也可以帮助我们判断是否重新执行render函数
import { memo } from 'react'
const Home = memo(function(props) {
return <h1>{props.message}</h1>
})
export default Home
2.4 不可变的力量
在开发过程中,不要直接修改state当中的值(对象类型),可以先进行一个拷贝再通过setState进行修改,如果直接修改state中的值,比如 this.state.books.push(newBook); this.setState({ books: this.state.books}) ,这时如果组件继承至PureComponent,那么组件将不会执行render函数进行相应的更新;因为直接修改this.state.books
时,此时PureComponent会认为前后的state是相同的(对象引用相同)那么就不会执行render函数
3. ref
3.1 ref的作用
ref可以用来获取DOM元素或组件实例(类组件),进行某些操作
- 使用 react 库中的 createRef() 创建一个ref对象,在React元素中使用ref绑定该对象,那么元素对象就会被放到ref对象的current属性当中(该方法也能绑定组件实例)
- 给ref属性传入一个回调函数,在对应元素被渲染之后,回调函数会执行并传入该元素对象
3.2.2 函数组件使用ref的方式(ref转发)
ref不能应用与函数式组件,因为函数式组件没有实例,不能获取到对应的组件实例,但可以通过ref转发的方式获取到函数式组件中的元素
使用forwardRef高阶函数
4. 受控和非受控组件
4.1 受控组件
4.2 非受控组件
非受控组件就是表单数据交由 DOM 节点来处理
- 使用 ref 获取非受控组件中的表单数据(因为不能绑定 value ,否则会变成受控组件)
- 使用 defaultValue 属性来设置非受控组件的默认值
5. 高阶组件
5.1高阶组件的定义
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是基于 React 的组合特性而形成的设计模式
具体而言:高阶组件是参数为组件,返回值为新组件的函数
高阶组件在一些 React 第三方库中很常见:
5.2.2 登录鉴权
5.2.3 生命周期劫持
计算渲染时间
可以利用高阶组件来劫持生命周期,在生命周期中完成自己的逻辑
5.3 高阶函数的意义
优点:利用高阶组件可以针对某些 React 代码进行更加优雅的处理
缺点:
- HOC 需要在原组件上进行包裹或嵌套,若大量使用 HOC,会产生大量嵌套,让调试变得非常困难
HOC 可以劫持 props,在不遵守约定的情况下会造成冲突
6. Portals
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀方案,类似于Vue3的teleport
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素、字符串或 fragment
- 第二个参数(container)是一个 DOM 元素,即需要挂载的元素
7. fragment
fragment 允许将子列表分组,无需向 DOM 添加额外节点。比如每次返回元素时都要写一个 div 元素表示根节点,可以使用 fragment 将其分组就无需写 div 元素,fragment 也不会被渲染。
fragment 短语法
- <></>
- 当需要在 fragment 中添加 key 时,就不能使用短语法
8. StrictMode
8.1 StrictMode的定义
StrictMode 是一个用来突出显示应用程序中潜在问题的工具(严格模式)
- 与 fragment 一样,StrictMode 不会渲染任何可见UI
- 它为其后代元素触发额外的检查和警告
- 严格模式检查仅在开发模式下运行,不会影响生产构建
8.2 StrictMode的作用
开启严格模式后的检测
- 识别不安全的生命周期
- 识别过时的ref API
- 检测额外的副作用。组件的 constructor 会被调用两次,这是严格模式下故意进行的操作,为了查看调用多次时是否会产生其它副作用,在生产环境中不会调用两次
- 检测废弃的 findDOMNode 方法
- 检测过时的 context API
五、React Transition Group(React动画)
react-transition-group 是由社区维护的一个提供css动画的第三方库,这个库可以很方便地实现组件的入场和离场动画,使用时安装即可
# npm
npm install react-transition-group —save
# yarn
yarn add react-transition-group
react-transition-group 主要包含四个组件:
- Transition
- 该组件是一个和平台无关的组件(不一定要结合CSS)
- 前端开发中,主要结合CSS来完成样式,所以比较常用的是CSSTransition
- CSSTransition
- 使用 CSSTransition 来完成过渡动画
- SwitchTransition
- 两个组件显示和隐藏切换时,使用该组件
TransitionGroup
开始状态:对于的类是 -appear、-enter、-exit
- 执行动画:对应的类是 -appear-active、-enter-active、-exit-active
- 执行结束:对应的类是 -appear-done、-enter-done、-exit-done ```css .show-appear, .show-enter { opacity: 0; }
.show-appear-active, .show-enter-active { opacity: 1; transition: opacity 2s ease; }
.show-exit { opacity: 1; }
.show-exit-active { opacity: 0; transition: opacity 2s ease; }
![image.png](https://cdn.nlark.com/yuque/0/2022/png/26749762/1667492399055-d89a908e-f7a0-4a06-a7f9-f0b1e816de8b.png#averageHue=%23282d35&clientId=uad0657e4-395b-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u1d6d58f9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=294&originWidth=875&originalType=binary&ratio=1&rotation=0&showTitle=false&size=34692&status=done&style=none&taskId=u7092bd86-1bef-4117-9a42-e7e858ff51d&title=)
<a name="XrZ1l"></a>
### 1.2 CSSTransition常见属性
CSSTransition常见属性:
- in:触发进入或者退出状态(boolean)
- 如果添加了 unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉
- 当 in 为 true 时,触发进入状态,会添加 -enter、-enter-active类开始执行动画,当动画执行结束后,会移除两个class,并且添加 -enter-done类
- 当 in 为 false 时,触发退出状态,会添加 -exit、-exit-active类开始执行动画,当动画执行结束后,会移除两个class,并且添加 -exit-done类
- classNames:决定了动画 class 的名称
- timeout:决定类添加或移除的时间(一般与动画时间一致)
- appear:是否在初次进入时添加动画(需要和 in 同时为 true)
- unmountOnExit:退出动画后是否卸载组件
<a name="sXYHf"></a>
### 1.3 CSSTransition钩子函数
CSSTransition 对应的钩子函数:主要为了检测动画的执行过程并完成一些 JavaScript 操作
- enter
- onEnter:在开始**进入**动画之前触发
- onEntering:在执行**进入**动画时触发
- onEntered:在执行**进入**动画结束后触发
- exit
- onExit:在开始**离开**动画之前触发
- onExiting:在执行**离开**动画时触发
- onExited:在执行**离开**动画结束后触发
<a name="Fy5jG"></a>
## 2. SwitchTransition
SwitchTransition可以完成两个组件间切换的动画
<a name="d32Ep"></a>
### 2.1 SwithTransition常见属性
SwithTransition常见属性:
- mode
- in-out:表示新组件先进入,旧组件再移除
- out-in:表示旧组件先移除,新组件再进入(默认)
<a name="BvIVI"></a>
### 2.2 SwithTransition的使用
- SwithTransition 组件内要用 CSSTransition 或者 Transition 组件对需要切换的组件进行包裹
- SwithTransition 里面的 CSSTransition 或 Transition 不再像以前那样接受 in 属性来判断元素是何种状态,取而代之的是 key 属性
```css
.login-enter {
transform: translateX(-100px);
opacity: 0;
}
.login-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.login-exit {
transform: translateX(0);
opacity: 1;
}
.login-exit-active {
transform: translate(100px);
opacity: 0;
transition: all 1s ease;
}
3. TransitionGroup
当我们有一组动画时,需要将这些 CSSTransition 放入到 TransitionGruop 中来完成动画
六、React中的CSS
1. 组件化开发中的CSS
组件化中 CSS 需要符合的条件:
- 可以编写局部CSS:CSS具备自己的作用域,不会随意污染其它组件内的元素
- 可以编写动态的CSS:可以获取当前组件的一些状态,根据状态变化生成不同的CSS样式
- 支持所有的CSS特性:伪类、动画、媒体查询等
-
1.1 Vue中编写CSS
Vue中CSS编写的特点:
Vue 通过在 .vue 文件文件中使用 标签来编写自己的样式
- 通过 scoped 属性来决定样式是全局还是局部
- 通过 lang 属性来设置预处理器less、sass
-
2. React中CSS编写方式
2.1 内联样式
内联样式是官方推荐的一种CSS样式写法:
style接受一个采用小驼峰命名属性的 JavaScript 对象,而不是CSS字符串
- 并且可以引用 state 中的状态来设置相关样式
内联样式的优点:
- 样式之间不会产生冲突
- 可以动态获取state中的状态
内联样式的缺点:
- 代码提示差,写法上都需要使用驼峰标识
- 大量的内联样式会造成代码混乱
- 某些样式无法编写(如伪类/伪元素)
2.2 普通的CSS
这种编写方式就是写一个单独的样式文件,通过import引入,但这种方式都属于全局的CSS,样式之间会相互影响
2.3 CSS Modules
2.3.1 CSS Modules的介绍
- CSS Modules并不是 React 特有的解决方案,而是所有使用了类似于 webpack 配置的环境下都可以使用(配置webpack.config.js中modules: true)
- React脚手架已经内置了CSS Modules的配置
- .css/.less/.scss等样式文件都需要修改成 .module.css/.module.less/.module.scss等
- 使用import引入,并通过模块的方式使用
- CSS Modules解决了局部作用域的问题
2.3.2 CSS Modules的缺点
- 引用的类名不能使用连接符,在JavaScript中是不识别的
- 所有的className都必须使用 {style.className} 的形式来编写
- 不方便动态修改样式,依然需要使用内联样式的方式
2.3.3 CSS Modules的使用
```css .title { font-size: 32px; color: ‘blue’; }
2.4 CSS in JS(styled-components)
CSS in JS 是指一种模式,其中CSS由JavaScript生成而不是在外部文件中定义,它是由第三方库提供;简单来说,CSS in JS就是将CSS也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态
- CSS in JS 通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等
- 比较流行的 CSS in JS 库
- 使用
styled
创建样式组件,可使包裹的元素应用样式 - 可接收外部传入的props
- 可以通过attrs设置默认样式
- 可以从外部引入变量
- 设置样式主题,共享样式属性
- 支持样式的继承
3. 动态添加class
最简单的就是通过判断来动态添加class,但这样做会导致代码可读性差,代码冗余等问题
我们可以借助第三方库:classnames
七、Redux
1. Redux的核心思想
1.1 理解JavaScript纯函数
在程序设计中,若函数符合以下条件,则为纯函数
- 确定的输入,一定会产生确定的输出
- 函数在执行过程中,不能产生副作用
副作用:表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响,比如修改了全局变量,修改外部参数或者修改外部的存储
在React当中无论是声明函数式组件还是类组件,都要像纯函数一样保护它们的props不被修改
1.2 为什么需要Redux
- JavaScript开发的应用程序越来越复杂,这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等;也包括一些UI状态,比如某些元素是否被选中、是否加载动画效果、保存当前分页等
- 管理不断变化的state是非常困难的,状态之间会相互依赖,一个状态的变化会引起另一个状态的变化,也会影响view页面的变化,当应用程序复杂时,state 在什么时候,因为什么原因发生了变化,发生了什么变化,会变得非常难以控制和追踪
- React只是在视图层帮我们解决了DOM的渲染过程,但state依然需要我们自己来管理
Redux就是一个帮助我们管理state的容器:Redux是 JavaScript 的状态容器,提供了可预测的状态管理
Redux除了和React一起使用之外,还可以与其它界面库一起使用,比如:Vue
1.3 Redux的核心理念
1.3.1 store
1.3.2 action
Redux要求我们通过action来更新数据:
- 所有数据的变化,必须通过派发(dispatch)action来更新(为了使数据可预测、可追踪)
- action 是一个普通的 JavaScript 对象,用来描述这次更新的 type 和 content
1.3.3 reducer
reducer是一个纯函数,它做的事情就是将传入的state和action结合起来生成一个新的state
两个参数:
- 参数一:store中目前保存的state
- 参数二:本次需要更新的action(dispatch传入的action)
返回值:它的返回值会作为store之后存储的state
1.4 Redux的三大原则
1.4.1 单一数据源
- 整个应用程序的state被存储在一颗object tree中,并且整个object tree只存储在一个 store 中
- Redux并没有强制让我们不能创建多个store,但是那样并不利于数据的维护
单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改
1.4.2 State是只读的
唯一修改state的方法一定是触发action,不要试图在其他地方通过任何的方式来修改state
- 这样就确保了view或网络请求都不能直接修改state,它们只能通过action来描述如何修改state
这样可以保证所有的修改都被集中化处理,并安装严格的顺序来执行,无需担心race condition(竞态)问题
1.4.3 使用纯函数来执行修改
通过reducer将旧state和actions联系在一起,并且返回一个新的state
- 随着应用程序的复杂度增加,可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分
- 所有的reducer都应该是纯函数,不能产生任何副作用
2. Redux的使用详解
2.1 基本使用
- 创建一个对象作为初始状态(initialState)
- 创建store,并创建reducer函数
- 通过action来修改state
- 通过dispatch来派发action
- 被派发的对象会被传到reducer函数的第二个参数中
- 在reducer中处理代码,reducer是纯函数,不能直接修改state
- stroe/index.js:创建store,导出store
- store/actionCreators.js:存放各种action
- store/constants.js:将 action 的 type 进行抽取,使用常量的方式保证代码的一致性
- store/reducer.js:定义初始化数据,编写reducer函数逻辑
2.3 Redux使用过程
React中Redux使用过程
- 创建store、reducer,并初始化state
- 组件在componentDidMount中订阅store
- 组件可以主动派发action去更新state,在reducer中执行相应的逻辑,返回新的state,并触发订阅
当state发生变化的时候,调用setState更新数据(调用render函数重新渲染)
3. react-redux
3.1 react-redux的基本使用
使用
Provider
包裹 App 组件(Provider和context类似能将store中state的数据共享给包裹的组件,因为所有组件可能都需要使用,那么就包裹App组件)
- 在组件中通过
connect
函数来进行store映射;connect函数是一个高阶组件,它内部的操作和我们手动使用redux一样,connect将这些逻辑抽取出来写成高阶组件,可以根据映射关系,将所需的state数据和dispatch映射到组件中
- 在prop中获取所需的state和派发action的函数进行使用