React教程 (笔记总结)

React

官网

A JavaScript library for building user interfaces.

用于构建用户界面的 JavaScript 库

create-react-app

脚手架,用于构建项目的基本结构

$ npx create-react-app <proj-name>
# 或
$ yarn create react-app <proj-name>

在创建项目时,会自动创建出项目的基本结构,安装项目中使用到的依赖包资源,主要用到的依赖包:

  • react:核心包
  • react-dom:用于浏览器渲染
  • react-scripts:包装了 webpack 相关的配置

npm scripts

{
    
    
  "scripts": {
    
    
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
}
  • start:启动开发环境下的任务,会自动打开开发服务器 webpack-dev-server
  • build:生产环境的任务,构建生产环境下的代码
  • test:测试任务
  • eject:弹出,将 webpack 的配置弹出到项目根目录下,这种弹出是不可撤销的

JSX

JSX 不是字符串内容,也不是 HTML 的结构,而是一个 JS 的表达式,可以将其理解为是一个 JavaScript 的语法扩展。

建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。

在 JSX 的表达式中如果需要插入 JS 表达式内容,可使用 {} 语法结构:

const element = <h1>Hello { 'world' }</h1>

JSX 中使用 {} 插入 JS 表达式时,会进行转义处理,如果不希望转义而是需要渲染 html 文本,则可以使用 dangerouslySetInnerHTML 属性进行设置,该属性值是一个对象,对象中必须有 __html 属性绑定需要渲染的 html 文本:

<div dangerouslySetInnerHTML={
   
   {__html: html}}></div>

原理

JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖,JSX 最终会经过 Babel 插件转译为 React.createElement() 的调用。

<div className="container">
    <ul className="list">
    	<li>1</li>
        <li className="active">2</li>
    </ul>
    <span>
    	<a href="/test.html">链接</a>
    </span>
</div>

经过 Babel 转译:

React.createElement(
	'div',
    {
    
     className: 'container' },
    React.createElement(
    	'ul',
        {
    
     className: 'list' },
        React.createElement(
        	'li',
            null,
            '1'
        ),
        React.createElement(
        	'li',
            {
    
     className: 'active' },
            '2'
        )
    ),
    React.createElement(
    	'span',
        null,
        React.createElement(
        	'a',
            {
    
     href: '/test.html' },
            '链接'
        )
    )
)

函数调用后的执行结果:

{
    
    
    type: 'div',
    props: {
    
    
        className: 'container'
    },
    children: [
        {
    
    
            type: 'ul',
            props: {
    
     className: 'list' },
            children: [
                {
    
    
                    type: 'li',
                    props: null,
                    children: ['1']
                },
                {
    
    
                    type: 'li',
                    props: {
    
     className: 'active' },
                    children: ['2']
                }
            ]
        },
        {
    
    
            type: 'span',
            props: null,
            children: [
                {
    
    
                    type: 'a',
                    props: {
    
     href: '/test.html' },
                    children: ['链接']
                }
            ]
        }
    ]
}

虚拟 DOM 比较算法:

  • diff
  • fiber

组件

函数组件

function Element(props) {
    return <h1>Hello { props.greeting }</h1>
}
  • 组件名称采用帕斯卡命名规范(每个单词首字母都大写)
  • 函数参数 props 是一个对象,表示的是组件在使用时接收到的属性
  • 函数体内部使用 return 返回一个 JSX 所表示的 React 元素
  • 在 JSX 表达式中可使用 <Element propName="propValue"> 标签来使用组件结构

class 组件

// class 组件
class MyElement extends React.Component {
  render() {
    return <h1>Hello { this.props.greeting }</h1>
  }
}
  • 组件名称采用帕斯卡命名规范
  • 继承自 React.Component 父类
  • 必须实现 render() 方法,在方法体内部返回 React 元素
  • render() 方法体内部可使用 this.props 获取组件接收到的属性。props 是从父类中继承过来的
  • 在 JSX 表达式中可使用 <MyElement propName="propValue"> 标签来使用组件结构

React.Fragment

function TodoHeader() {
  return (
    <div>
      <h1>主标题</h1>
      <h2>副标题</h2>
    </div>
  )
}

无论是函数组件,还是 class 组件,在渲染元素节点时,都需要使用单个根元素节点将 UI 布局结构包裹起来。

可以使用 <React.Fragment> 组件将需要布局的结构包裹起来,这样在实际渲染的 DOM 树结构中就不会有多余的节点显示:

// 函数数组
function TodoHeader() {
  return (
    <React.Fragment>
      <h1>主标题</h1>
      <h2>副标题</h2>
    </React.Fragment>
  )
}

<React.Fragment></React.Fragment> 可简写为 <></>

组件通信

父子组件

  • 父传子:使用 props 属性
  • 子传父:利用 props 属性,在父组件中使用子组件时,传递父组件中一个函数引用作为属性值,在子组件中需要传递数据时,调用从属性中接收到的函数,在调用函数时传递所需要传输的数据作为参数。

跨组件层级

  • 转换为父子组件通信
  • Context
  • reudx、mobx…

prop-types

运行时类型检测的工具,在定义组件时,使用该工具可以验证属性类型是否合法

$ npm i prop-types
  // 运行时属性类型检测
  static propTypes = {
    todos: PropTypes.array
  }

state

state 是组件内部私有要使用到的数据(类似于在 vue 组件中的 data)

修改 state 中的数据需要使用 setState() 方法来修改,页面才会响应式渲染

关于 setState() 的三件事:

  • 不要直接修改 state,而是使用 setState() 来修改
  • setState() 的更新可能是异步的
  • state 的更新会被合并

表单

为单行文本框双向绑定数据(类似于 vue 中使用 v-model,但是 react 中没有这种指令):

绑定 value 属性、处理 change 事件,来实现数据的双向绑定

Ref

  • 字符串类型的 ref(不推荐,已过时)
  • React.createRef() :创建 ref 对象,关联组件或DOM节点,可使用该 ref 对象的 current 属性获取组件实例或 DOM 对象

Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context 主要用于在全局范围内实现组件间的资源共享。

API

  • React.createContext():创建 Context 对象
  • Provider 组件:用于在 Context 对象上保存数据,有一个 value 属性
  • Consumer 组件:消费在 Provider 组件上绑定的数据,Consumer 需要传递一个函数子元素
  • Class.contextType:为类设置 contextType 静态(static)属性后,可以使用 this.context 获取最近 Context 上保存的数据

生命周期

class 组件中的生命周期钩子函数:

挂载阶段

  • constructor():初始化:state、绑定事件处理程序中的 this
  • render():渲染
  • componentDidMount():在挂载结束后,可获取 DOM 树结构,可发送网络请求

componentWillMount():已过时,不推荐使用,如果确实需要使用,可使用 UNSAFE_componentWillMount()

更新阶段

  • shouldComponentUpdate():通常用于优化,React.PureComponent
  • render():渲染
  • componentDidUpdate():更新后

卸载阶段

  • componentWillUnmount():即将卸载

高阶组件(Higher Order Component)

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式(装饰者设计模式)。

高阶组件是参数为组件,返回值为新组件的函数。

Hook

在 react 16.8 中新增的功能。使得在函数组件中可以使用到类似 class 组件中的 state 等特性。

Hook 函数都是以 use 作为前缀开头,hook 函数只能用在函数组件或自定义 hook 的函数中。

state hook

useState()

effect hook

useEffect()

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

// componentDidMount()
useEffect(callback, [])

// componentDidUpdate()
// 当依赖项 item1, item2 发生改变时,会重新执行 callback 函数
useEffect(callback, [item1, item2])

useEffect(() => {
    
    
  // statements
  // TODO...
  
  // 该返回的函数就相当于是 componentWillUnmount()
  return () => {
    
    }
}, [])

其它 hook

useCallback()

useMemo()

useRef()

Redux

官方中文文档

是一个状态管理的库,本身与 React 没有任何关系

概念

  • store:仓库
  • state:状态,全局共享的状态数据
  • reducer:纯函数,用于进行状态的同步更新。会接收 state 与 action 作为参数,在函数体内部根据 action 的 type 判断进行何种状态更新,返回更新后的新的状态对象
Reducer 必需符合以下规则:
- 仅使用 `state``action` 参数计算新的状态值
- 禁止直接修改 `state`。必须通过复制现有的 `state` 并对复制的值进行更改的方式来做 *不可变更新(immutable updates)*- 禁止任何异步逻辑、依赖随机值或导致其他“副作用”的代码
  • dispatch:函数,用于触发调用 reducer() 更新状态。更新 state 的唯一方法是调用 store.dispatch() 并传入一个 action 对象
  • action:是一个普通对象,用于描述发生了什么事情,通常有 type、payload 属性
  • action creator: action creator 是一个创建并返回一个 action 对象的函数。它的作用是让你不必每次都手动编写 action 对象

Redux 期望所有状态更新都是使用不可变的方式

使用

安装 redux

$ npm i redux
# 或
$ yarn add redux

创建 reducer

一个应用中可以有多种类别的状态需要进行管理,针对不同类别的状态编写各自独立的 reducer,如:

// counter
// 初始化状态数据
const initialState = {
    
    
  count: 0
}

/**
 * reducer 函数,用于同步更新状态数据
 * @param {*} state 当前状态信息
 * @param {*} action Action 对象,有 type 与 payload 属性
 * @returns 更新后的状态对象
 */
const counterReducer = (state = initialState, {
     
      type, payload }) => {
    
    
  // 复制 state 状态数据
  const copy = {
    
     ...state }
  // 根据 action.type 判断,修改复制后的数据
  switch (type) {
    
    
    case 'INCREMENT': // 加
      copy.count += 1
      return copy
    case 'DECREMENT': // 减
      copy.count -= 1
      return copy
    default:
      return state
  }
}

export default counterReducer

将各独立的 reducer 合并为一个大的根 reducer:

import {
    
     combineReducers } from 'redux'
import counterReducer from './counter'
import userReducer from './user'

// 将各独立的 reducer 合并为一个根 reducer
const rootReducer = combineReducers({
    
    
  counter: counterReducer,
  user: userReducer
})

export default rootReducer

创建 action creator 函数

action creator 主要用于创建 action 对象,以方便复用:

import {
    
     DECREMENT, INCREMENT } from "./contants";

/**
 * action creator,用于创建 action 对象
 * @returns 
 */
export const incrementAction = () => ({
    
    
  type: INCREMENT
})

export const decrementAction = () => ({
    
    
  type: DECREMENT
})

创建 store

import {
    
     createStore } from 'redux'
import rootReducer from '../reducers'

// 创建 Store
const store = createStore(rootReducer)

// 导出
export default store

安装 react-redux 绑定库

redux 与 react 没有任何关系,如果需要在 react 中使用到 redux 的 store,则需要安装 react-redux 绑定库:

$ npm i react-redux
# 或
$ yarn add react-redux

利用 Provider 保存 store

利用 react-redux 中提供的 Provider 组件来保存 redux 中的 store,通常在应用的入口 index.js 文件中引入:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

在组件中使用 redux 的状态数据

使用 react-redux 中提供的 connect() 方法,在组件中连接 redux 的 store:

import {
    
     connect } from 'react-redux'
import {
    
     decrementAction, incrementAction } from './actions/counter'

class App extends React.Component {
    
    
    // ...
}

// mapStateToProps 是一个函数,该函数传递 state 作为参数,
// 函数的返回值是一个对象,该返回对象中的属性会被合并到组件
// 的 props 中。
// 每当 store 中 state 状态被修改后,该方法都会被调用
const mapStateToProps = state => ({
    
    
  count: state.counter.count
})
// const mapStateToProps = state => {
    
    
//   return {
    
    
//     count: state.counter.count
//   }
// }

// mapDispatchToProps 是一个函数,该函数传递 dispatch 作为
// 参数,函数体内部返回一个对象,返回对象的属性也会被合并到
// 组件的 props 中
const mapDispatchToProps = dispatch => ({
    
    
  increase() {
    
    
    dispatch(incrementAction())
  },
  decrease() {
    
    
    dispatch(decrementAction())
  }
})

// connect() 调用后,返回的是一个 HOC
const hoc = connect(mapStateToProps, mapDispatchToProps)

export default hoc(App)

connect() 函数用于在组件中连接redux的store。

connect() 接收两个可选的参数:mapStateToPropsmapDispatchToProps

connect() 的返回值是一个 HOC (高阶组件),调用该 HOC 函数,将已有组件 (如:App) 作为参数传递,返回新组件。

HOC 高阶组件是利用 装饰器模式 对传递的参数组件进行功能增强:将 mapStateToProps()、mapDispatchToProps() 返回对象的属性合并到组件的 props 中

异步状态更新

可以使用 redux-thunk 这个中间件来实现异步状态更新

// TODO…

前端路由

在 SPA 单页面应用程序中,当需要实现界面切换效果(页面导航)时,通过 JavaScript 来完成在同一个页面中不同界面间的切换(DOM操作),这时就需要使用到前端路由

模式

  • hash:在URL中使用 # 标识,当 hash 值改变时(window.onhashchange)切换界面显示
  • history:URL中没有显式的 #,其 url 的格式与服务端路由的格式是完全一致的,因此,如果使用 history 模式的路由,还需要服务端的配置支持。利用了 h5 中 history 新增的 API 实现其功能

不管使用 hash 模式还是 history 模式,在进行导航切换时,都不会向服务端发送请求

React Router

官网

包资源:

  • react-router:核心包
  • **react-router-dom:**浏览器中渲染时使用到的路由包
  • react-router-native:原生应用中使用到的路由包
  • react-router-config:辅助用于进行静态路由配置时使用的包

安装

$ npm i react-router-dom
# 或
$ yarn add react-router-dom

组件API

  • HashRouter: hash 模式路由
  • BrowserRouter: history 模式路由
  • Route: 配置路由-路由路径path、渲染组件component。可以有 component 与 render 属性来渲染组件,不要同时使用 component 与 render,当同时出现时,render 会被忽略
  • Redirect: 重定向
  • Link: 导航(链接)跳转
  • Switch: 多分支选择(匹配 path 路径)

UI 组件库

官网

安装

$ npm i antd
# 或
$ yarn add antd

引入 antd 组件与样式

import { Button } from 'atnd'

import 'antd/dist/antd.css'

高级配置 - craco

安装 craco

$ npm i @craco/craco -D
# 或
$ yarn add @craco/craco --dev

修改 package.json 中的 npm scripts

{
    
    
  "scripts": {
    
    
    "start": "craco start",
    "build": "craco build",
    "test": "craco test"
  },
}

在项目根下创建 craco.config.js 文件:

module.exports = {
    
    }

安装 craco-less 包:

$ npm i craco-less --save-dev
# 或
$ yarn add craco-less --dev

修改 craco.config.js 文件:

const CracoLessPlugin = require('craco-less')

module.exports = {
    
    
  plugins: [
    {
    
    
      plugin: CracoLessPlugin,
      options: {
    
    
        lessLoaderOptions: {
    
    
          lessOptions: {
    
    
            modifyVars: {
    
     // 自定义主题,修改 antd 使用到的 less 变量值
              '@primary-color': '#1DA57A'
            },
            javascriptEnabled: true,
          },
        },
      },
    },
  ],
}

课堂案例

在线教育类产品后台管理系统

前端:

功能:

  • 登录
  • 首页-仪表盘
  • 课程管理
    • 课程列表
    • 编辑课程
    • 回收站

技术栈:

  • create-react-app + react.js + react-router + redux + react-redux + redux-thunk
  • axios
  • antd
  • echarts
  • 富文本编辑器:wangeditor

工作流程

  • 分配工位与计算机,熟悉公司各种规章制度

  • 配置开发环境,安装前端开发过程中使用到的工具,如 git、nodejs、vscode(webstorm)、小程序开发者工具、hbuilderx、PS…

开发流程:

  • 本地没有源代码,下载源代码到本地
$ git clone <repo.url>

本地已有源代码,则直接更新到最新版本

$ git pull origin xxx
  • 在工作空间中实现编码
  • 在完成一个比较完整功能并自测通过后时,提交版本库
$ git status
$ git add -A
$ git commit -m 'flag: message'
  • 推送到远程中央仓库
$ git push origin xxx
  • 如果存在版本冲突问题,需要解决冲突

项目搭建流程:

  • 利用脚手架(vue CLI 、create-react-app),搭建项目基本结构

  • 安装项目基础依赖的包资源,如前端路由相关的包、状态管理相关的包、网络请求相关的包、UI组件库相关的包

    • react-router-dom、redux、react-redux、redux-thunk、axios、antd、vant、element-ui
  • 创建项目目录结构

project
|--.git # 隐藏目录,git 版本库管理
|--public # SPA 的 index.html
|--src # 项目源代码文件夹
|--|--assets # 静态资源,如图片、音频、视频等媒体资源文件
|--|--api # 与网络请求访问相关的代码,或 requests 目录名
|--|--components # 应用中需要复用的组件
|--|--actions # redux 的 action creator 代码
|--|--reducers # redux 的 reducer 代码
|--|--store # 状态管理库使用到的仓库代码
|--|--router # 路由相关的目录,或 routes 目录名
|--|--utils # 辅助工具代码
|--|--views # 页面组件代码,或 pages 目录名
|--|--styles # 外部样式表文件资源
|--|--scss # css 预处理器资源
|--|--App.vue / App.jsx / App.js # 应用的外层组件
|--|--main.js / index.js # 项目的入口JS文件
|--doc # 项目文档
|--test # 测试代码
|--package.json
|--README.md # 项目描述文件
|--.gitignore # git 在版本管理时需要忽略的资源
  • 编写通用代码,如:axios 二次封装、redux中通用的 reducer、action creator、createStore() 等结构、路由相关通用代码
  • 实现业务功能编码

猜你喜欢

转载自blog.csdn.net/Robergean/article/details/120161862