概述
最近复习了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',
}
生命周期函数
生命周期函数指在某一个时刻会自动调用执行的函数。而react的生命周期语义很是清除,如图大抵划分了四个时刻。
- initialization 初始时,可以认为是组件的构造函数,即 constructor()。
- Mounting 挂载页面时
- Updation 数据更新时
- UnMounting 页面卸载时
而其中挂载页面时和数据更新时如图分别对应了一些流程,实际上通过语义即可得知其义。如 Updation 发现props发生改变时,会依照以下流程去逐个调用:
- 组件接收到props要改变的数据(新的props)
- 组件是否应该更新,如果返回false就代表不进行渲染,反之渲染
- 组件将要更新数据
- 组件执行 render() 重新挂载页面
- 组件数据更新完毕
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 的具体使用了。一切以实际为主。
安装与使用
安装
yarn add redux
yarn add react-redux
使用
上图流程图主体部分为四个,大题描述了redux的操作流程。即由组件通过 Action Creators 向 Store 发起某种指令, 而 Store 仅作为仓库不知如何执行这个指令,于是将仓库的数据发给 Reducers 并要求去执行这个指令,当 Reducers 执行完毕后返回 Store 一个被修改后的数据,然后更新仓库中的数据。
因此大抵问题可以解构成:
- 如何创建仓库
- 如何处理指令
- 如何使用仓库获得资源及发起指令
其实很简单。首先创建仓库,创建文件 /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>
本页的评论功能已关闭