初尝试 React & React-Redux & React-Router & 及项目开发所用类库

概述

最近复习了Java相关的知识,以及各个框架的使用,于是准备动手编写一个项目。但由于已经几个月没有写代码了,尤其对vue产生了很大的陌生感,虽说从代码层次而言并非不能写,但不知为什么总是有种抗拒感。

我想大抵是因为,我想要的是通过vue去得到一个结果,而并不打算向复习这个过程投入过多的时间,然而我又必须投入一些时间去复习。这便是我的脑回路,随后索性直接花了两天时间学习了一套react的课程,便诞生了此文。

简述react与安装

由Facebook推出,为全球使用人数最多的前端框架,拥有庞大且活跃的社区,而其的开发特点是普遍采用函数式编程。与vue相比,虽说有的地方感觉会麻烦一些,但如果熟悉Java的话,应该会写起来很舒服。

想要安装它,可以直接使用 create-react-app 脚手架。

yarn global add create-react-app

基础实例与说明

入口文件 index.js

其实主要思路很好理解,无非首先创建一个名为 App 的组件,然后使用 ReactDom.render 将其挂载在 #root 之中。不过有一点要说明的是在 .render() 方法中所要编写的是一些标签元素。而这些标签元素可以称之为JSX,其用处是在JS之中允许编写 JS && HTML, 即可以通过js去动态的绑定一些数据或对其操作。

而编写JSX必须要引入React包,这也是明明没有操作 ‘React’ 却要引入它的缘故。

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App' // 引入同目录下的 App 组件
// 将React的主要工作空间设置在id为'root'的元素之中
const root = ReactDOM.createRoot(document.getElementById('root'));
// 向工作空间挂载 'App' 组件
root.render(
    <App /> // JSX 语法
);

组件的创建与动态渲染

如代码注释所示,创建class App 继承于 react.Component 便可声明为react的组件。其后在 render() 中的JSX代码中使用 单花括号绑定 state 中的属性。相当于vue中的 v-bind

import react, { Component } from "react";

// 继承 react.Component 便可声明为react的组件
class App extends Component {
    constructor(props) { // 类的构造方法

        // 规定写法,大概是如果想要使用props元素,则必须通过super从父类继承到this指向。此处不太明白
        super(props); 

        // 规定所有值必须存放在state对象之中
        this.state = {
            value: '',
        }
    }

    // 该组件要渲染的内容,react每次检测到state或props中值的变换,将重新调用render函数
    render() {
        return (
            
            // 使用 {} 单花括号指代引用值
            <p>{this.state.value}</p>

        )
    }

}

export default App;

元素设置事件与绑定this指向

一般情况下,为了区别于表单中的事件,因此JSX中的事件为驼峰式命名。其后可以参考前述的使用单个花括号绑定对应方法。因此就可以明白,其实单花括号内部内容会被解释成JS运行。因此我们可以将其修改时间绑定至 inputChangeHandler 方法之上。

    <input value={this.state.value} onChange={this.inputChangeHandler}></input>

而 inputChangeHandler 会接收到一个对象e,其中 e.target 即为input对象的dom。其后的重点在于,我们需要通过 setState() 方法去更新state之中的数据,而不可以直接调用state去修改。

    inputChangeHandler(e) {
      this.setState(() => {
        return {
          value: e.target.value
        }
      })
    }

但最终仍然会出现问题,state的上下文处于 constructor() 之中,而 render() 则由react内部机制共享了this,但我们的方法却没有对应的this指向,因此根本无法获得state对象以及 setState() 方法。

因此我们一般会在 constructor() 方法之中提前强制将我们自定方法的this,指向到 constructor() 中的this。

    this.inputChangeHandler = this.inputChangeHandler.bind(this);

父子组件通信

父子通讯的手段其实总的来说是两种。

其一是父向子传递属性,子通过props进行获取,而如果子想修改props之中的数据,则调用父传递过来的方法。当然这个方法的this指向绑定的是父组件的,仅作为提供子组件执行。

而第二个手段便是采用全局统一的 “数据仓库”。当某个组件修改了某个数据,那么绑定对应数据的组件便收到通知。此类实现便是后续要说的redux。等价于vue中的vuex。

如下代码所示,有以下几个重点:

  • 在react之中,元素的class为了不和js的类产生歧义或冲突,改成了className。
  • 通过 list.map 可以将原本的数组映射为一组名为 TodoItem 的组件,而一般React中组件均是以大写开头的
  • 直接对 TodoItem 添加对应属性即可将数据绑定到上面,但值得注意的是,传递的 delFunc 方法必须要绑定当前组件的this指向,只有这样子组件才能正常修改props之中对应的父组件数据。


  <div className="list">
    <ul>
      {
        this.state.list.map((item, index) => {
          return <TodoItem index={index} text={item} delFunc={this.itemDelHandler} />
        })
      }
    </ul>
  </div>

其后在子组件之中即可直接通过 props 获得对应的数据,且通过 props.delFunc 去调用父组件函数去修改数据。

render() {
    const { text, index } = this.props
    return (
        <li key={index} onClick={this.itemDelHandler.bind(this, index)}>{text}</li>
    )
}

itemDelHandler(index) {
    this.props.delFunc(index);
}

子组件属性类型限制与默认数值

首先要引入类型的标识库,该类中的字段可以标识为某个类型。其后在当前类的 .propTypes 进行对某个数据进行

import PropTypes from 'prop-types'

TodoItem.propTypes = {

    // 字符串或数字
    text: PropTypes.oneOfType(PropTypes.string, PropTypes.number),
    index: PropTypes.number,

    // 方法
    delFunc: PropTypes.func,
}

而默认数据则是 .defaultProps。

TodoItem.defaultProps = {
    testDefault: 'hello world',
}

生命周期函数

生命周期.png

生命周期函数指在某一个时刻会自动调用执行的函数。而react的生命周期语义很是清除,如图大抵划分了四个时刻。

  • initialization 初始时,可以认为是组件的构造函数,即 constructor()。
  • Mounting 挂载页面时
  • Updation 数据更新时
  • UnMounting 页面卸载时

而其中挂载页面时和数据更新时如图分别对应了一些流程,实际上通过语义即可得知其义。如 Updation 发现props发生改变时,会依照以下流程去逐个调用:

  1. 组件接收到props要改变的数据(新的props)
  2. 组件是否应该更新,如果返回false就代表不进行渲染,反之渲染
  3. 组件将要更新数据
  4. 组件执行 render() 重新挂载页面
  5. 组件数据更新完毕

shouldComponentUpdate 的使用

根据react挂载的规则可知,当父组件由于数据更新而重新挂载页面的时候,子组件亦会被重新挂载,但我们的子组件却不一定发生了数据改变。那么我们根据 shouldComponentUpdate 的定义,可在此阶段判断子组件是否发生了数据改变,如果没有,那么我们返回false以免被刷新,从而优化性能。

shouldComponentUpdate(newProps, newState) {
    return this.props.text != newProps.text;
}

( 佩罗西你怎么还不来... 2022/08/02 无心学习

react-redux

首先,redux 是一个独立的js类库,并不被 react 所包含。其主要作用在于解决多组件互相传输数据的问题。而其后所说的 redux-thunk 以及 redux-saga 可以使 redux “升级”。一般称其为 redux 的中间件。

而 react-redux 是在 redux 的基础上进行封装的工具,我们一般都是直接使用 react-redux,因此便不记录 redux 的具体使用了。一切以实际为主。

安装与使用

redux基本流程.png

安装

yarn add redux
yarn add react-redux

使用

上图流程图主体部分为四个,大题描述了redux的操作流程。即由组件通过 Action Creators 向 Store 发起某种指令, 而 Store 仅作为仓库不知如何执行这个指令,于是将仓库的数据发给 Reducers 并要求去执行这个指令,当 Reducers 执行完毕后返回 Store 一个被修改后的数据,然后更新仓库中的数据。

因此大抵问题可以解构成:

  1. 如何创建仓库
  2. 如何处理指令
  3. 如何使用仓库获得资源及发起指令

其实很简单。首先创建仓库,创建文件 /store/index.js. 可以发现创建仓库需要 reducer。

import { createStore} from 'redux'
import reducer from './reducer'

const store = createStore(reducer);

export default store;

于是创建文件/store/reducer.js。reducer的主要作用就是根据指令类型去做某种操作,这里的主要问题是一般为了规范性,我们需要将指令的类型名称设置为常量。

// 设置state默认值
const defaultState = {
    currentTodoNum: 0,
};

export default (state = defaultState, action) => {

    // 根据redux规则,不可直接修改state的数据,因此通过两次序列化得到state的副本
    const newState = JSON.parse(JSON.stringify(state));

    // 根据action的类型判断如何操作state对应的数据
    if(action.type === 'change_current_todo_num'){
        newState.currentTodoNum = action.val;
        return newState;
    }

    return state;
};

在创建好前两者后,就可以使用它们了。首先要在想要使用redux的组件根目录引入 react-redux 包下的 Provider,并引入刚才创建的 store。这时是将组件与redux进行绑定

import { Provider } from 'react-redux'
import store from './store/index'

之后在根目录组件用 Provider 进行包裹并绑定 store。这里的重点是,使用Provider所在的组件不可以对redux进行操作,否则会报错

<Provider store={store}>
    <App /> 
</Provider>

绑定完毕之后,如果子组件想要使用store中的内容的话,需要使用 react-redux 下的 connect 对子组件进行连接

import { connect } from 'react-redux'

而 connect 方法需要两个参数,分别是 mapStateToProps 和 mapDispatchToProps。mapStateToProps 用处是将store中的state的一些你需要的数据,映射到props之中。而 mapDispatchToProps 是提供一个 dispatch 对象供你执行指令。

const mapStateToProps = (state) => {
  return {

    // 那么当前组件就可以得到 this.props.currentTodoNum
    currentTodoNum: state.currentTodoNum,
  }
}
const mapDispathToProps = (dispath) => {
  return {

    // 通过 this.props.changeCurrentTodoNum() 执行
    changeCurrentTodoNum(num) {

      /*
      action = {
        type: 类别,
        val: 数据
      }
      */
      const action = {
        type: 'change_current_todo_num',
        val: num
      }

      // 执行action
      dispath(action)
    }
  }
}

配置上以上两个参数后,抛出被 connect 连接的组件。

export default connect(mapStateToProps, mapDispathToProps)(App);

使用 immutable 优化 redux 中的 state

前面说一般情况下在reducer操作state的时候,不允许直接去操作state,但并没有对其进行限制,所以一般情况下我们可以引入一个名为 immutable 的类对其进行封装,通过它去做获得数据和修改数据。

yarn add immutable

引入immutable包下的 fromJS, 其大意为包装一个js对象。

import { fromJS } from 'immutable'

// 设置state默认值
const defaultState = fromJS({
    currentTodoNum: 0,
});

那么reducer中的修改逻辑就变为:

export default (state = defaultState, action) => {

    // 根据action的类型判断如何操作state对应的数据
    if (action.type === 'change_current_todo_num') {
        return state.set('currentTodoNum',action.val)
    }
    
    return state;
};

而获得数据变为:

  const mapStateToProps = (state) => {
    return {
      // 那么当前组件就可以得到 this.props.currentTodoNum
      currentTodoNum: state.get('currentTodoNum'),
    }
  }

PureComponent 的使用

如果redux中使用了immutable.js,那么可以将继承 Component 改为 PureComponent,这样组件就默认拥有了#3-5-1-shouldComponentUpdate-的使用 的相同效果。

根据模块拆分 reducer 并使用 combineReducers 连结

配合 redux-thunk 使用

配合 redux-saga 使用

使用 react-redux

styled-components 限制CSS样式

react-router

react-router-dom 是在 react-router 的基础上加入了一些组件。

安装

yarn add react-router-dom

使用

import { BrowserRouter, Route, Routes } from 'react-router-dom'

root.render(

    // 在路由根目录外亦可使用别的组件,路由转变时只是更改 BrowserRouter 组件之中的内容
    <Header/>

    // 路由根目录
    <BrowserRouter>
      <Routes>
        {/* 使用 element 元素设置要访问的组件 */}
        <Route path="/" element={<App/>}>
          {/* 在根目录的基础上,增加date参数 */}
          <Route path='/:date' element={<App/>}></Route>
        </Route>
      </Routes>
    </BrowserRouter>    
  );

而我们在其路由中组件,亦可控制及获取路由数据。最主要的是控制页面跳转与获取路径参数。以下所写的 useParams 及 useNavigate 为Hooks之中的概念衍生的方法,因此下列代码仅适用于函数式组件

  import { useParams, useNavigate } from 'react-router-dom'

那么使用 useParams() 便可获取当当前路由的参数。


  const { date } = useParams();

而 useNavigate() 所得到的对象,通过它可以实现跳转(导航)功能。

  const navigate = useNavigate()

  navigate('/')

亦可通过 Link 标签模拟一个a标签进行跳转。主要目的是在不跳转的前提下,仅挂载路由地址对应的组件

  <Link to='/test'>
    <div>hello world</div>
  </Link>

嵌套组件

当需要在某个组件下嵌套子组件时,需要将path加上 " /* "

<BrowserRouter>
    <Routes>
      <Route path="/MyTask/*" element={<MyTask />}></Route>
    </Routes>
</BrowserRouter>

其后在组件之中直接使用Routes和Route就好, 需要注意如果使用 "/" 代表绝对路径, 而不带才是相对路径。

<Routes>
    <Route path="/" element={<p>23333</p>}></Route>
    <Route path="AcceptedTask" element={<AcceptedTask />}></Route>
</Routes>

redux-toolkit 的使用

# React