我的React学习笔记

一. 前言

这篇文章算是我学习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使用过程:

  1. 创建一个对象,作为要保存的状态
  2. 创建store来存储这个state
    • 创建store时必须创建reducer
    • 可以通过store.getState来获取当前的state
  3. 通过action来修改state
    • 通过dispatch来派发action
    • 通常action中都会有type属性,可以携带其他数据
  4. 修改reducer中的处理代码
    • reducer是一个纯函数,不要直接修改state
  5. 可以在派发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))
        })
    }
}

九十九. 未完,待更新

还在学习中,持续更新

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值