一. 前言
这篇文章算是我学习React的一个笔记,内容并不是特别详细,且大部分代码为纯手打,可能会有错误,敬请谅解
二. react脚手架
2.1 Create React App
通过Create React App是用 React 创建单页应用的最佳方式。
npx create-react-app my-app
cd my-app
npm start
2.2 脚手架项目目录结构
2.3 脚手架中的webpack
react脚手架将webpack相关的配置隐藏起来了,如果想要看到webpack的配置可以执行package.json文件中的一个脚本:“eject”: “react-scripts eject”,但该操作不可逆
npm run eject
2.4 脚手架项目中react的入口
项目中的入口为src下的index.js文件,其核心代码为
import ReactDom from 'react-dom/client'
import App from './App'
const root = ReactDom.createRoot(document.getElementById('root'))
root.render(<App />)
三. 类组件
3.1 类组件的约定
- 首字母大写
- 必须继承 React.Component 父类
- 类组件中必须要有一个 render 方法
- render 方法必须有返回值
3.2 类组件示例
class Button extends React.Component {
constructor(props){
super(props); // 调用父类constructor构造器,当定义constructor时必须调用super,且要在使用 this 前调用。
// super(props) 等同于 super(); this.props = props;
this.state = {
message: 'Hello React!',
};
}
handleClick(){
console.log('handleClick')
}
render(){
const { message } = this.state;
return (
<button onClick={() => this.handleClick()}>{message}</button>
)
}
}
四. 事件绑定
按照惯例,通常将事件处理程序命名为 handle,后接事件名。你会经常看到 onClick={handleClick} , onMouseEnter={handleMouseEnter} 等。
末尾添加 Capture 为捕获阶段事件。极少数情况下,你可能需要捕获子元素上的所有事件,即便它们阻止了传播。例如你可能想对每次点击进行埋点记录,传播逻辑暂且不论。那么你可以通过在事件名称末尾添加 Capture 来实现这一点,例:
<div onClickCapture={handleClick}>...</div>
事件对象下 event.stopPropagation 为阻止事件冒泡, event.preventDefault 为阻止默认行为。
五. 类组件中回调函数this绑定
5.1 在constructor中显示绑定
class App extends React.Component {
constructor(){
super()
this.btnClick = this.btnClick.bind(this)
}
btnClick(){}
}
5.2 ES6 class fields
class App extends React.Component {
btnClick = () => {}
}
5.3 直接传入箭头函数
推荐这种方式,可以传入额外参数
class App extends React.Component {
btnClick(){}
render() {
return (
<button onclick={e => this.btnClick()}></button>
)
}
}
六. 条件渲染
6.1 通过if渲染
一般用于代码复杂场景
const {isShow} = this.state;
let element;
if(isShow) {
element = <div>显示内容</div>
}
return (
<div>
{element}
</div>
)
6.2 通过三目运算符渲染
一般用于代码简单场景
const {isShow} = this.state;
return (
<div>
{isShow ? <div>内容1</div> : <div>内容2</div>}
</div>
)
6.3 通过逻辑与(&&)运算符
一般用于代码简单且仅需控制是否显示
const {isShow} = this.state;
return (
<div>
{isShow && <div>loading...</div>}
</div>
)
七. 列表渲染
在 React 中通常使用 filter() 筛选需要渲染的组件和使用 map() 把数组转换成组件数组。
通常要在循环中加入key属性,以进行性能优化。
const {list} = this.state;
return (
<div>
<ul>
{list.filter(item => item.age > 18).map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
)
八. 类组件中的常用生命周期
8.1 componentDidMount
componentDidMount()在组件已经挂载到dom上后立即调用
通常进行以下操作:
- 依赖于dom的操作可以在这里进行(如echarts)
- 发送网络请求
- 添加一些订阅(要在componentWillUnmount取消订阅,否则可能会造成内存泄漏)
8.2 componentDidUpdate
componentDidUpdate()会在更新后立即调用,首次渲染不会执行此方法
8.3 componentWillUnmount
componentWillUnmount()会在组件卸载及销毁之前直接调用
- 清除timer
- 清除在componentDidMount中添加的订阅
8.4 不常用的生命周期
- getDerivedStateFromProps:让组件在 props 变化时更新 state;该方法返回一个对象来更新state;
- getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);
- shouldComponentUpdate:用于判断数据更新时dom是否更新,PureComponent 的实现原理
8.5 生命周期图示
8.5.1 基础生命周期
8.5.2 展示不常用生命周期
九. 组件嵌套及传值
9.1 组件嵌套
组件内部可以直接渲染另一个组件
class HelloWorld extends React.Component {
render(){
return <div>hello world!!!</div>
}
}
class App extends React.Component {
render(){
return (
<div>
<h1>App component</h1>
<HelloWorld></HelloWorld>
</div>
)
}
}
9.2 父子组件通讯
9.2.1 父组件传子组件
- 父组件通过 属性=值 的形式来传递给子组件数据;
- 子组件通过 props 参数获取父组件传递过来的数据;
9.2.2 参数propTypes
从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库
原生类型规则如下
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol
}
更多验证方式参考:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html
类组件验证
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
函数组件验证
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
export default HelloWorldComponent
默认值可以使用 defaultProps
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};
9.2.3 子组件传递父组件
在react中通过props传递消息,父组件给子组件传递一个回调函数,然后在子组件中调用这个函数
class CounterButton extends React.Component {
render(){
const {onButtonClick} = this.props
return <button onclick={e => onButtonClick()}></button>
}
}
class App extends React.Component {
handleClick(){
console.log('按钮被点击!')
}
render(){
return (
<div>
<h1>App component</h1>
<CounterButton onButtonClick={() => this.handleClick()}></CounterButton>
</div>
)
}
}
9.3. 插槽
9.3.1 组件的children子元素
优点:结构清晰
缺点:当通过索引获取传入的元素时很容易出错
class NavBar extends React.Component {
render(){
const {children} = this.props
// 当children中只有一个元素时为这个元素自身
// 当children中有多个元素时为数组
return (
<div className="nav-bar">
<div className="left">
{children[0]}
</div>
<div className="center">
{children[1]}
</div>
<div className="right">
{children[2]}
</div>
</div>
)
}
}
class App extends React.Component {
render(){
return (
<div>
<NavBar>
<button>按钮</button>
<h2>标题</h2>
<span>span</span>
</NavBar>
</div>
)
}
}
9.3.2 props实现插槽
通过props可以实现具名插槽的效果
class NavBarTwo extends Component {
render() {
const { leftSlot, centerSlot, rightSlot } = this.props
return (
<div className='nav-bar'>
<div className="left">{leftSlot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightSlot}</div>
</div>
)
}
}
export class App extends Component {
render() {
const btn = <button>按钮2</button>
return (
<div>
<NavBarTwo
leftSlot={btn}
centerSlot={<h2>呵呵呵</h2>}
rightSlot={<i>斜体2</i>}
/>
</div>
)
}
}
9.3.3 作用域插槽
可以通过props传递函数来模拟作用域插槽
class NavBar extends React.Component {
render() {
const {list, itemType} = this.props
return (
<div>
{list.map(item => {
return (
<div key={item.id}>
{itemType(item)}
</div>
)
})}
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
navBarList: [{
id: 1,
name: '待收货'
}, {
id: 2,
name: '已收货'
}]
}
}
render(){
return (
<div>
<NavBar
list={this.state.navBarList}
itemType={item => (
<span>{item.name}</span>
)}
></NavBar>
</div>
)
}
}
十. 非父子通讯
10.1 context
共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言
10.1.1 React.createContext
通过createContext创建一个需要共享的context对象,参数为默认值
import React from "react"
// 1.创建一个Context
const ThemeContext = React.createContext({ color: "blue", size: 10 })
export default ThemeContext
如果使用context,但该组件不是Context.Provider 子组件的时候使用默认值defaultValue
10.1.2 Context.Provider
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。特点:
- Provider 接收一个 value 属性,传递给消费组件
- 一个 Provider 可以和多个消费组件有对应关系
- 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据
- 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染
root.render(
<UserContext.Provider value={{nickname: "kobe", age: 30}}>
<ThemeContext.Provider value={{color: "red", size: "30"}}>
<App/>
</ThemeContext.Provider>
</UserContext.Provider>
)
10.1.3 Class.contextType
挂载在 class 上的 contextType 属性会被赋值为一个由 React.createContext() 创建的 Context 对象。
然后就可以在class组件内使用this.context来读取Context对象的值。
export class Profile extends Component {
render() {
console.log(this.context)
return (
<div>Profile</div>
)
}
}
Profile.contextType = ThemeContext
10.1.4 Context.Consumer
使用Context.Consumer组件也可以订阅到context变更,这样就可以在函数式组件中完成订阅context。
需要函数作为子元素,这个函数接收当前的context值,返回一个React节点。
Context.Consumer组件可以嵌套使用。
function HomeBanner() {
return <div>
{/* 函数式组件中使用Context共享的数据 */}
<ThemeContext.Consumer>
{
value => {
return <h2> Banner theme:{value.color}</h2>
}
}
</ThemeContext.Consumer>
</div>
}
什么时候使用 Context.Consumer:
- 当使用value的组件是一个函数式组件时
- 当组件中需要使用多个context时
10.2 event-bus
非父子通讯也可以使用时间总线来进行通讯,如hy-event-bus
首选安装hy-event-store库
npm install hy-event-store
创建实例并导出
import { HYEventBus } from "hy-event-store"
const eventBus = new HYEventBus()
export default eventBus
在需要的位置注册监听事件,页面销毁时记得注销监听,否则可能会导致内存泄漏
import React form 'react'
import eventBus form '../utils/event-bus'
class Home extends React.Component {
handleNext(){}
componentDidMount(){
eventBus.on('next', this.handleNext)
}
componentWillUnmount(){
eventBus.off('next', this.handleNext)
}
}
注册触发事件
import React form 'react'
import eventBus form '../utils/event-bus'
class Banner extends React.Component {
handleClick(){
eventBus.emit('next')
}
render(){
return (
<div>
<button onclick={e => this.handleClick()}>下一个</button>
</div>
)
}
}
十一. setState
11.1 setState用法
- setState({})
- setState((state, props) => { return {} })
- setState(updater, callback)
// 1. 直接调用
this.setState({
name: '张三'
})
// 2. 传入一个回调函数
this.setState((state, props) => {
return {
name: '张三'
}
})
// 3. 传入第二个参数,将在数据更新完成后调用
this.setState({
name: '张三'
}, () => {
console.log('数据已经更新完成')
})
11.2 setState是异步
setState一般来说是异步的,优势:
- 多个updater放在同一次更新中,只执行一次render函数,提高性能
- 保证在state没有被更新的时候,state/props保持一致
什么情况下是同步的:
- 在React18之前,setTimeout / Promise.then回调 / 原生DOM事件回调中是同步的
- 在React18之后,可以使用flushSync强制同步更新,但不推荐使用
// 使用flushSync强制同步更新,但不推荐使用
flushSync(() => {
this.setState({ name: "张三" })
})
console.log(this.state.name) // 张三
十二. 高阶组件
12.1 高阶组件的定义
高阶组件不是React API的一部分,它是基于React的组合特性而形成的设计模式。高阶组件类似于高阶函数,高阶函数的定义:至少满足一下条件之一:
- 接收一个或多个函数作为参数
- 输出一个函数
js中比较常见的filter、map、reduce等都是高阶函数
高阶组件的英文是Higher-Order Components,简称HOC,官方的定义:高阶组件是参数为组件,返回值为新组件的函数。
解析后:
- 高阶组件本身不是一个组件,而是一个函数
- 这个函数参数时一个组件
- 这个函数返回一个组件
12.2 高阶组件的使用
高阶组件有很多应用场景,比如:
-
不改变原有代码的情况下增加新的props
-
生命周期劫持
-
利用高阶组件来共享Context,如:
import ThemeContext from "../context/theme_context"
function withTheme(OriginComponment) {
return (props) => {
return (
<ThemeContext.Consumer>
{
value => {
return <OriginComponment {...value} {...props}/>
}
}
</ThemeContext.Consumer>
)
}
}
export default withTheme
12.3 高阶组件的缺点
- 高阶组件需要在原组件上进行包裹或者嵌套,如果大量使用将会产生非常多的嵌套,调试变得非常困难
- 高阶组件可以劫持props,在不遵守约定的情况下也可能造成冲突
十三. SCU性能优化
13.1 默认Component组件
在继承自Component的class组件中默认是没有实现shouldComponentUpdate方法,所以只要调用setState,render函数就一定会被执行。
所以想要实现优化操作就要在shouldComponentUpdate中自己实现。
class App extends React.Component {
shouldComponentUpdate(nextProps, nextState){
// 在这里对进行this.props和nextProps、this.state和nextState进行对比
// 当返回true时会进行更新(运行render函数),返回false则不进行更新
return true;
}
}
13.2 PureComponent
PureComponent 类似于 Component ,但是当 props 和 state 不发生改变时会跳过重新渲染。
在PureComponent内部,它实现了一个shouldComponentUpdate方法,用于判断组件是否需要更新。这个方法默认会对组件的state和props进行浅比较,如果没有变化,那么就会返回false,阻止重新渲染。
所以当对Object、Array类型进行改变时应该对数据进行拷贝,以防止数据改变时页面没有进行更新:
let obj = {...this.state.obj, name: '张三'}
this.setState({obj})
错误写法:
let obj = this.state.obj
obj.name = '张三'
this.setState({obj})
13.3 memo
memo是一个高阶组件,它可以对包裹的组件的props进行浅层比较,以比较结果来决定是否重新渲染组件。通常用于函数组件。
import {memo} from 'react'
function App() {
return (
<div>
<h1>App Component</h1>
</div>
)
}
export default memo(App)
十四. ref
14.1 ref获取DOM
14.1.1 在元素上绑定一个ref字符串
不推荐,该方式已弃用,虽然现在可以用,但在以后的更新中会删除
class App extends PureComponent {
componentDidMount(){
console.log(this.refs.titleRef) // 直接打印dom元素,不用.current
}
render(){
return (
<div>
<div ref="titleRef">Hello World</div>
</div>
)
}
}
14.1.2 提前创建好ref对象
当ref属性用于HTML元素时,构造函数中使用React.creatRef() 创建的ref接收底层DOM元素作为其current属性
import {PureComponent, createRef} from 'react'
class App extends PureComponent {
constructor(){
super()
this.titleRef = createRef();
}
componentDidMount(){
console.log(this.titleRef.current) // dom元素绑定在current上
}
render(){
return (
<div>
<div ref={this.titleRef}>Hello World</div>
</div>
)
}
}
14.1.3 ref传入一个回调函数
class App extends PureComponent {
constructor(){
super()
this.titleRef = null
}
componentDidMount(){
console.log(this.titleRef) // 直接打印dom元素
}
render(){
return (
<div>
<div ref={el => this.titleRef = el}>Hello World</div>
</div>
)
}
}
14.2 ref获取类组件实例
当ref属性用于自定义class组件时,ref对象接收组件的挂载实例作为其current属性
不能在函数组件上使用ref属性,因为函数组件没有实例。
(ref获取DOM的三种方式对于类组件同样适用)
class App extends PureComponent {
constructor(){
super()
this.testRef = createRef()
}
componentDidMount(){
console.log(this.testRef.current)
}
render(){
return (
<div>
<h1>App Component</h1>
<HelloWorld ref={this.testRef} />
</div>
)
}
}
14.3 ref获取函数组件的DOM
函数组件没有实例对象,所以不能将ref直接绑定到函数组件上,但可以通过React.forwardRef绑定在函数组件中的某个DOM元素上。
forwardRef是一个高阶组件,当函数组件被forwardRef包裹后将会传入第二个参数ref。
import {forwardRef, PureComponent, createRef} from 'react'
const HelloWorld = forwardRef((props, ref) => {
return (
<div>
<h1 ref={ref}>HelloWorld Component</h1>
<div>
function component
</div>
</div>
)
})
class App extends PureComponent {
constructor(){
super()
this.hwRef = createRef()
}
componentDidMount(){
console.log(this.hwRef.current) // 打印HelloWorld组件中的h1标签
}
render(){
return (
<div>
<h1>App component</h1>
<HelloWorld ref={this.hwRef} />
</div>
)
}
}
十五. 受控组件
在 HTML 中,表单元素(如、 和 )通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
简单来说就是被react状态维护的表单组件。
15.1 input、textarea
input、textarea比较类似,都是绑定value、name属性,监听onChange,然后在回调函数中将value值设置给对应的state值
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
inputValue: '',
textareaValue: '',
}
}
handleChange(event) {
const {target} = event;
this.setState({
[target.name]: target.value
})
}
render(){
return (
<div>
<input name="inputValue" value={this.state.inputValue} onChange={e => this.handleChange(e)} />
<textarea name="textareaValue" value={this.state.textareaValue} onChange={e => this.handleChange(e)} />
</div>
)
}
}
15.2 checkbox
15.2.1 checkbox 单选
class App extends PureComponent {
constructor(props){
super(props)
this.state = {
isAgree: false,
}
}
handleAgreeChange(event){
this.setState({isAgree: event.target.checked})
}
render(){
const {isAgree} = this.state
return (
<div>
<label>
<input type="checkbox" checked={isAgree} onChange={e => this.handleAgreeChange(e)} />
<span>同意协议</span>
</label>
</div>
)
}
}
15.2.2 checkbox多选
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
fruits: [{
id: 1, value: 'lime', label: '酸橙'
}, {
id: 2, value: 'mango', label: '芒果'
}],
favorite: {},
}
}
handleFavoriteChange(event) {
const {target} = event
const {favorite} = this.state
this.setState({
favorite: {...favorite, [target.name]: target.checked}
})
}
render() {
const { favorite, fruits } = this.state
return (
<div>
{
fruits.map(fruitItem => {
return (
<label key={fruitItem.id}>
<input type="checkbox" name={fruitItem.value} checked={!!favorite[fruitItem.value]}
onChange={e => this.handleFavoriteChange(e)} />
<span>{fruitItem.label}</span>
</label>
)
})
}
</div>
)
}
}
15.3 select
15.3.1 select单选
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
selectValue: '',
}
}
handleChange(event) {
const {target} = event;
this.setState({
[target.name]: target.value
})
}
render(){
return (
<div>
<select name="selectValue" value={this.state.selectValue} onChange={e => this.handleChange(e)}>
<option value="lime">酸橙</option>
<option value="mango">芒果</option>
</select>
</div>
)
}
}
15.3.2 select多选
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
fruits: [{
id: 1, value: 'lime', label: '酸橙'
}, {
id: 2, value: 'mango', label: '芒果'
}],
favorite: [],
}
}
handleFavoriteChange(event) {
const favorite = Array.from(event.target.selectedOptions, item => item.value)
this.setState({
favorite
})
}
render() {
return (
<div>
<select value={this.state.favorite} onChange={e => this.handleFavoriteChange(e)} multiple>
<option value="lime">酸橙</option>
<option value="mango">芒果</option>
</select>
</div>
)
}
}
十六. Fragment
import { Fragment } from 'react';
<Fragment></Fragment>
简写:<></>
Fragment 组件允许你将子元素分组,而不会在 HTML 结构中添加额外的节点
注:列表循环中 key 必须绑定在 Fragment 上,不能用简写
十七. react中的“传送门”–Portal
使用Portal可以将子节点渲染到存在于父组件以外的DOM元素
import { PureComponent } from 'react'
import { creatPortal } from 'react-dom'
class Modal extends PureComponent {
render(){
return creatPortal(this.props.children, document.getElementById('modal'))
}
}
class App extends PureComponent {
render(){
<div>
<h1>App Component</h1>
<Modal>
<h2>温馨提示</h2>
<div>这是提示内容</div>
</Modal>
</div>
}
}
十八. react的严格模式
使用 StrictMode 组件可以对其包裹的子组件启用严格模式。可以全局启用,也可以局部启用。
严格模式启用了以下仅在开发环境下有效的行为:
- 组件将 重新渲染一次,以查找由于非纯渲染而引起的错误。
- 组件将 重新运行 Effect (生命周期)一次,以查找由于缺少 Effect 清理而引起的错误。
- 组件将被 检查是否使用了已弃用的 API。
全局启动:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
const root = ReactDom.createRoot(document.getElementById('root'))
root.render(
<StrictMode>
<App />
</StrictMode>
)
十九 react中css写法
19.1 内联样式
优点:
- 不会产生样式冲突
- 动态设置
缺点:
- 驼峰写法
- 有些样式没提示
- 大量样式代码臃肿混乱
- 无法编写如伪类、伪元素的样式等
class App extends PureComponent {
constructor(props){
super(props)
this.state = {
color: 'red'
}
}
render(){
const { color } = this.state
return (
<div>
<div style={{fontSize: '18px', color: color}}>App Component</div>
</div>
)
}
}
19.2 单独css文件
缺点:都属于全局css,容易造成样式冲突
注意:css中的class类名在这里写作className
/* App.css */
.title {
font-size: 18px;
color: red;
}
/* App.jsx */
import './App.css'
class App extends PureComponent {
render(){
return (
<div>
<div className="title">App Component</div>
</div>
)
}
}
19.3 css modules
css modules并不是react特有的,所有类似于webpack配置环境下都可以使用,react脚手架已经内置了css modules的配置。
注意:.css、.less、.scss后缀的样式文件都需要改成.module.css、.module.less、.module.scss
优点:解决了局部作用域问题
缺点:
- 类名不能使用连接符“-”,如:.home-title
- 所有className都必须要用{ style.className }的形式来编写
/* App.module.css */
.title {
font-size: 18px;
color: red;
}
.footer {
text-align: center;
}
/* App.jsx */
import appStyle from './App.module.css'
class App extends PureComponent {
render(){
return (
<div>
<div className={appStyle.title}>App Component</div>
<div className={appStyle.footer}>Footer Component</div>
</div>
)
}
}
19.4 css in js(styled-components)
css in js是react编写css最受欢迎的一种解决方案,而styled-components是其中比较流行的一个库。
styled-components使用标签模板语法
标签模板用法:
let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
标签模板更多详细用法可以参考阮一峰的ECMAScript 6入门这本书,电子版:https://es6.ruanyifeng.com/#docs/string#%E6%A0%87%E7%AD%BE%E6%A8%A1%E6%9D%BF
安装
npm install styled-components
styled-components简单用法:
/* style/variables.js */
export const primaryColor = "#ff8822"
export const secondColor = "#ff7788"
export const smallSize = "12px"
export const middleSize = "14px"
export const largeSize = "18px"
/* style.js */
import styled from 'styled-components'
// 使用变量
import { primaryColor, largeSize, middleSize } from './style/variables.js'
export const StyledSection = styled.div`
.title {
font-size: ${largeSize};
color: ${primaryColor};
}
`
// 样式继承
export const StyledHeader = styled(StyledSection)`
background: #e8e8e8;
`
// 使用自定义组件
const Text = ({className, children}) => (
<span className={className}>
{ children }
</span>
)
export const StyledText = styled(Text)`
font-size: ${middleSize}
`
// 接收外部传入的props
export const StyledFooter = styled.div.attrs(props => ({
// 可以在这里对数据进行处理
color: props.color || 'blue',
}))`
border: 1px solid #ccc;
.title {
font-size: ${props => props.size}px;
color: ${props => props.color};
&:hover {
color: red;
}
}
`
/* App.jsx */
import { StyledSection, StyledHeader, StyledText, StyledFooter } from './style.js'
class App extends PureComponent {
constructor(props){
super(props)
this.state = {
color: '#888',
size: 14
}
}
render(){
const {color, size} = this.state
return (
<div>
<StyledSection>
<div className="title">StyledSection Title</div>
</StyledSection>
<StyledHeader>
<div className="title">StyledHeader Title</div>
</StyledHeader>
<StyledText>StyledText Component</StyledText>
<StyledFooter color={color} size={size}>
<div className="title">StyledFooter Title</div>
</StyledFooter>
</div>
)
}
}
更多使用方法参考styled-components官网:https://styled-components.com/docs
19.5 classnames
react中动态修改class比较麻烦:
<h2 className={'title' + (isActive ? ' active' : '')}>Title</h2>
可以借助第三方库classnames来动态修改
安装
npm install classnames
import classnames from 'classnames'
class App extends PureComponent {
render(){
return (
<div>
<h2 className={classnames('title', {active: isActive})}>Title</h2>
</div>
)
}
}
更多用法:
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
// Arrays will be recursively flattened as per the rules above
const arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'
// dynamic class names
let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });
二十 Redux
20.1 redux使用过程
安装redux
npm install redux
redux使用过程:
- 创建一个对象,作为要保存的状态
- 创建store来存储这个state
- 创建store时必须创建reducer
- 可以通过store.getState来获取当前的state
- 通过action来修改state
- 通过dispatch来派发action
- 通常action中都会有type属性,可以携带其他数据
- 修改reducer中的处理代码
- reducer是一个纯函数,不要直接修改state
- 可以在派发action之前监听store的变化
/* store/index.js */
import { createStore } from 'redux'
const initialState = {
counter: 100,
user: {}
}
function reducer(state = initialState, action) {
switch(action.type) {
case 'change_counter':
return { ...state, counter: state.counter + action.num }
case 'change_user':
return { ...state, user: action.user }
default:
return state
}
}
const store = createStore(reducer)
export default store
/* App.jsx */
import store from './store'
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
counter: store.getState().counter
}
this.unsubscribe = null;
}
componentDidMount() {
// 监听store变化
this.unsubscribe = store.subscribe(() => {
const state = store.getState()
this.setState({ counter: state.counter })
})
}
compontentWillUnmount() {
// 取消store监听
this.unsubscribe();
}
handerCounterChange(num){
store.dispatch({
type: 'change_counter',
num
})
}
render() {
return (
<div>
<h2>counter: {this.state.counter}</h2>
<button onClick={e => this.handerCounterChange(1)}>counter +1</button>
<button onClick={e => this.handerCounterChange(-1)}>counter -1</button>
</div>
)
}
}
20.2 三大原则
- 单一数据源
- redux并没有强制不能创建多个store,但这样做不利于维护
- state是只读的
- 唯一修改state的方法一定是触发action,不要试图在其他地方通过任何方式来修改state
- 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心竞态的问题
- 使用纯函数来执行修改
- 通过reducer将旧state和actions联系在一起,并返回一个新的state
- 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducer,分别操作不同state tree的一部分
- 所有的reducer都应该是纯函数,不能产生任何的副作用
20.3 redux结构划分
可以将store、reducer、action、constants拆分成一个个文件
- store/index.js
- store/reducer.js
- store/actionCreators.js
- store/constants.js
/* store/constants.js */
export const CHANGE_COUNTER = 'change_counter'
export const CHANGE_USER = 'change_user'
/* store/actionCreators.js */
import * as actionTypes from './constants.js'
export const changeCounterAction = num => ({
type: actionTypes.CHANGE_COUNTER,
num
})
export const changeUserAction = user => ({
type: actionTypes.CHANGE_USER,
user
})
/* store/reducer.js */
import * as actionTypes from './constants.js'
const initialState = {
counter: 100,
user: {},
}
function reducer(state = initialState, action) {
switch(action.type) {
case actionTypes.CHANGE_COUNTER:
return { ...state, counter: state.counter + action.num }
case actionTypes.CHANGE_USER:
return { ...state, user: action.user }
default:
return state
}
}
export default reducer
/* store/index.js */
import { createStore } from 'redux'
import reducer from './reducer.js'
const store = createStore(reducer)
export default store
使用:
/* App.jsx */
import store from './store'
import { changeCounterAction } from './store/actionCreators'
// 获取counter
console.log(store.getState().counter)
// 触发action
store.dispatch(changeCounterAction(-5))
20.4 模块化
可以将reducer拆分成多个小的reducer,分别操作不同state tree的一部分
每个模块都有自己的index.js、reducer.js、actionCreators.js、constants.js
除了index.js的其他文件都与之前写法是一样的,具体的目录结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJYWbwNn-1689574880434)(react学习笔记.assets/image-20230714171329486.png)]
在各个模块中的index.js文件中将该模块中的reducer导出,并在store/index.js文件中进行reducer合并:
/* store/home/index.js */
import reducer from './reducer.js'
export default reducer
export * from './actionCreators.js'
/* store/index.js */
import { createStore, combineReducers } from 'redux'
import homeReducer from './home'
import userReducer from './user'
const reducer = combineReducers({
home: homeReducer,
user: userReducer
})
const store = createStore(reducer)
export default store
使用:
import store from './store'
import { changeCounterAction } from './store/home'
// 获取home模块下的counter
console.log(store.getState().home.counter)
// 触发action
store.dispatch(changeCounterAction(-5))
20.5 react-redux
react-redux是redux官方提供的辅助连接react的库。
安装:
npm install react-redux
使用:
/* index.js */
import ReactDOM from 'react-dom/client'
import App from './App'
// 1. 在index.js入口文件中引入react-redux库下的Provider组件
import { Provider } from 'react-redux'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'))
// 2. 将App组件包裹在Provider组件内,并将store作为props传给Provider组件
root.render(
<Provider store={store}>
<App />
</Provider>
)
/* App.jsx */
import { PureComponent } from 'react'
// 3. 引入connect高阶函数
import { connect } from 'react-redux'
import { changeCounterAction } from './store/home'
class App extends PureComponent {
render(){
return (
<div>
<h1>App Component</h1>
{ /* 7. 就可以在props中直接拿到数据 */ }
<div>counter:{ this.props.counter }</div>
<button onClick={e => this.props.changeCounter(1) }>counter +1</button>
<button onClick={e => this.props.changeCounter(-1) }>counter -1</button>
</div>
)
}
}
// 4. 定义需要哪些数据
const mapStateToProps = (state) => ({
counter: state.home.counter
})
// 5. 定义派发action的函数(不修改数据的可以不定义)
const mapDispatchToProps = (dispatch) => ({
changeCounter(num){
dispatch(changeCounterAction(num))
}
})
// 6. 将定义的两个函数传入connect,connect返回一个高阶组件,将需要数据的组件传入这个高阶组件内
export default connect(mapStateToProps, mapDispatchToProps)(App);
20.6 redux异步操作–redux-thunk
redux创建时可以传入第二个参数–中间件,redux-thunk就是官网推荐的、包括演示网络请求的中间。
安装:
npm install redux-thunk
使用:
/* store/index.js */
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import homeReducer from './home'
import userReducer from './user'
const reducer = combineReducers({
home: homeReducer,
user: userReducer
})
// 1. 创建redux时将redux-thunk作为中间件传入
const store = createStore(reducer, applyMiddleware(thunk))
export default store
/* store/user/actionCreators.js */
import * as actionTypes from './constants.js'
import axios from 'axios'
export const changeUserAction = user => ({
type: actionTypes.CHANGE_USER,
user
})
// 2. 将派发的action从对象改为函数
export const getUserAction = () => {
return (dispatch, getState) => {
// 3. 在函数内进行异步操作
axios.post('url').then(res => {
const user = res.data.data.user
// 4. 异步操作完成后重新派发action
dispatch(changeUserAction(user))
})
}
}
九十九. 未完,待更新
还在学习中,持续更新