# react-zero
**Repository Path**: lijinbode/react-zero
## Basic Information
- **Project Name**: react-zero
- **Description**: react 从零开始搭建,从入门到入土O(∩_∩)O哈哈~
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-04-16
- **Last Updated**: 2022-04-16
## Categories & Tags
**Categories**: Uncategorized
**Tags**: React, TypeScript, JavaScript
## README
# react-zero
[toc]
# 引言
react从零开始搭建,虽然说的是从零开始,但是那是不可能的^_^,搭建该react项目的目标是**使用 `react,typescript`技术栈然后用webpack构建一个基础的前端项目**用于学习react及其react的周边技术生态,项目搭建参考【[react-admin](https://github.com/yezihaohao/react-admin)】
在这之前也想学习react但是那时对vue都还不熟悉,对JavaScript和typescript的理解也很浅薄,所以这个学习计划就搁置了。如今已经被vue坑了一年多了(其实每天都在划水(~ ̄▽ ̄)~),现在面对vue的坑已经能够从容面对了。为了寻找被坑然后从坑中出来的爽快感同时提高自己的工作激情,决定开始学习react了哈哈。
# 基础构建
> 基础构建就是添加最基础的运行依赖,当然你想跳过这个阶段可以直接使用react官方的【[react-scripts](https://www.npmjs.com/package/react-scripts)】进行快速构建
## 添加git提交规范
不要问我为什么一来就添加这个东西,问就是 强迫症 犯了 `(●ˇ∀ˇ●)`哈哈
- 添加如下依赖
```js
{
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"commitizen": "^4.0.3",
"cz-conventional-changelog": "^3.0.2",
"husky": "^3.0.5",
}
```
- package.json中 添加配置
```js
{
"config": {
"commitizen": {
"path": "node_modules/cz-conventional-changelog"
}
},
"husky": {
"hooks": {
"commit-msg": "commitlint -e $GIT_PARAMS"
}
},
}
```
## 添加webpack
接下来我们开始,从零开始搭建webpack... ,算了吧!这样太慢了,让我们快进一下,直接搞个现成的吧
```js
{
"clean-webpack-plugin": "^3.0.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^4.4.1",
"portfinder": "^1.0.28",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.4.0"
}
```
## 添加资源解析loader
由于webpack只能解析js,所以什么css,图片,字体等都需要第三方loader来辅助解析
```js
{
"css-loader": "^3.4.2",
"file-loader": "^6.0.0",
"less": "^4.1.2",
"less-loader": "5.0.0",
"style-loader": "^1.1.3",
"url-loader": "^4.0.0",
}
```
然后在配置一下 `webpack.config.js` 文件
```js
module: {
rules: [
{ test: /\.(js|jsx)$/, use: 'babel-loader', exclude: /node_modules/ },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 对于外部引用的css不启用模块化
{ test: /\.scss$/, use: ['style-loader', 'css-loader?modules', 'sass-loader'] }, // css-loader启用模块化,解决css的模块作用域
{ test: /\.(ttf|woff|woff2|eot|svg)$/, use: 'url-loader' }
]
}
```
## 添加react
添加react,react-dom, 因为要解析jsx所以还要添加babel依赖
```js
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1"
"react": "^17.0.2",
"react-dom": "^17.0.2"
```
> 此时完成了项目的基础构建,目前这个项目可以完成
>
> 1. 模块化编译react为js
> 2. 静态资源引入(css,img等)
> 3. 输出html
>
>当然我们日常开发还需要,页面路由,公共状态管理,typescript,代码检查等
# 丰富项目配置
## 添加typescript
js开发开发的时候很舒服,但是修改和重构的时候真的太难了,如下组件
```jsx
import React from "react";
const Home = (props) => {
return (
{props.message}
)
}
export default Home
```
上面组件home有个属性`message`但是我们希望对这个属性的值或类型进行一些约束,这样方便提醒其他使用我们组件的开发者
>vue 中的组件属性验证是这样的
>
> props: {
> message: {
> type: String,
> default: ''
> }
> },
那么react中怎么实现参数约束和默认值呢,那么就要用到typescript了typescript的静态检查避免了一些在开发过程中就可能出现的错误
- 添加typescript
```js
{
"typescript": "^4.4.3",
}
```
- 添加ts说明文件
```js
"@types/react": "^17.0.27",
"@types/react-dom": "^17.0.9",
```
## 添加eslint
eslint将代码的错误尽可能的留在开发阶段,此乃开发必备
```js
{
"babel-eslint": "^10.1.0",
"@typescript-eslint/eslint-plugin": "^3.9.1",
"@typescript-eslint/parser": "^3.9.1",
"eslint": "^6.1.0",
"eslint-config-react-app": "^5.0.2",
"eslint-loader": "3.0.2",
"eslint-plugin-flowtype": "3.13.0",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.14.3",
"eslint-plugin-react-hooks": "^1.6.1",
}
```
添加`.eslintrc`配置文件
# react使用
之前都是搭建react开发框架,现在开始深入react的使用的,从使用中逐渐了解其设计思路,以及其周边生态
## react-router
无论是vue还是react,在开发过程中都不可避免的会接触到router的使用,那react-router是怎么工作的呢,接下来我们来看看吧
### 安装router和ts语法提示
```js
"react-router-dom": "^5.3.0"
"@types/react-router-dom": "^5.3.1",
```
### 创建路由
```tsx
import React from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import Home from '../pages/Home'
import Login from '../pages/Login'
import NotFound from '../pages/NotFound'
export default () =>
(
)
```
> 路由匹配参数
>
> exact 准确匹配路由
>
> exact strict 精准匹配路由含参数
>
> 注意:404页是通配规则要放在最后面!
### 路由跳转和传参
- 跳转
方式一:link标签跳转
```tsx
新闻列表
```
方式二:js跳转
```tsx
// props必须继承此接口,不然找不到方法
import { RouteComponentProps } from 'react-router';
type P = {} & RouteComponentProps
type S = {}
export default class NewsGroup extends React.Component {
// 跳转到 /news/detail 页并携带参数
linkNewsInfo() {
this.props.history.push({
pathname: '/news/detail',
search: 'id=1&name=tom'
})
}
}
```
- 传参
传参一般用的方式就三种
方式一:params传参
```jsx
路由页面: //配置 /:id
路由跳转并传递参数:
链接方式:XX
js方式:this.props.history.push('/demo/' + '2')
获取参数:this.props.match.params.id
```
> 优点:路由地址好看点,刷新不会丢失
>
> 缺点:路由页要配置,比较麻烦
方式二:search传参
```jsx
路由页面: //无需配置
路由跳转并传递参数:
链接方式:XX
js方式:this.props.history.push({pathname:'/demo',search:'id=1&name=tom'})
获取参数: this.props.location.search.name
```
>优点:刷新不会丢失
>
>缺点:参数较多时url地址很长,有点不美观
方式三:state传参
```jsx
路由页面: //无需配置
路由跳转并传递参数:
链接方式: XX
js方式:this.props.history.push({pathname:'/demo',state:{name:'dahuang'}})
获取参数: this.props.location.state.name
```
> 优点:可传递大量参数,且参数加密不可见
>
> 缺点:参数刷新会丢失
在路由跳转和传参上react和vue基本上差不多,只是vue写起来相对简单点
```js
this.$router.push({
path: 'pathName',
query: queryData
})
```
可以看到vue在内部封装得多点,用户使用起来要方便一点
### 路由拦截
vue的vue-router封装的很不错什么路由钩子函数,多层级选人,路由管理都有,但是react-router就只提供了基础功能,剩下的都需要自己封装 ,口水说干了直接看代码吧
由于react的router是以组件的形式存在的,所以他控制路由就有点想,控制组件的渲染一样,直接用js逻辑就可以控制
```tsx
// 此处省略代码若干行...
export default () => {
// 创建路由
const createRoute = (r: IFRouteItem) => {
const route = (r: IFRouteItem) => {
const ComponentItem = r.component
return (
{
// 包装 router
const wrapper =
const { onAuthLogin, beforeEnter } = r
if (beforeEnter) {
return beforeEnter(props, wrapper)
}
// 不需要登录校验的页面
if (onAuthLogin) {
return wrapper
// requireLogin方法判断是否登录成功,返回 Boolean
} else if (requireLogin(props)) {
return wrapper
} else {
return ;
}
}
}
/>
)
}
const subRoute = (r: IFRouteItem): any => {
return r.children && r.children.map((subR: IFRouteItem) => (subR.children ? subRoute(subR) : route(subR)))
};
return r.component ? route(r) : subRoute(r);
}
return (
{/* 渲染路由 */}
{staticRoutes.map(r => createRoute(r))}
)
}
```
### 多级路由嵌套封装
组件里面放路由即可,示例
```tsx
import React from "react";
import { Link } from 'react-router-dom';
import Routers from '../routes/index'
const App = () => {
return (
全局组件 - 返回首页
{}
)
}
export default App
```
对比vue来说vue直接在router里面配置就可以了
>优点:自由度高,可以玩出花样
>
>缺点:有点麻烦
## 组件
### 状态提升
- 子组件调用父组件方法
在官方的例子温度计中,状态提升就是子组件调用父组件的方法,这种方式在vue中就相当于
```js
this.$emit('fun', value)
```
在react这里父级已属性方式传入函数,然后子组件再调用也和vue的操作达到了同样的效果
```tsx
// 【父组件】
handleChange = (temperature: string, scale: string) => {
this.setState({ temperature, scale });
}
render() {
// 省略代码若干...
return (
);
}
// 【子组件】
// 调用父级事件 - 修改父级参数值
this.props.onChange(temperature, this.props.scale)
```
### 插槽
react的组件插槽和vue的类似,但是他更加灵活
```tsx
// 父 容器组件
export default class Mask extends React.Component {
render() {
const { show, close } = this.props
return (
{/* 单个插槽 */}
{this.props.children}
)
}
}
// 内嵌组件
export default class DialogContent extends React.Component {
render() {
return (
我是弹窗里面的内容
)
}
}
```
> 同时内嵌的内容也可以通过,props传入, 也可以传入渲染函数;反正很是灵活你想得到的方式都可以玩的出来
## Redux
### 基本存取流程
```js
React Component -> Action creaturs -> Store -> Reducers --> Store -> React Component
```
> 技术胖翻译如下
>
> ```js
> React Component 客官
> Action creaturs 妈妈桑
> Store 小姐姐候选单
> Reducers 看小姐姐忙不忙
> ```
### 注意点
- reducer必须是纯函数
> 纯函数
>
> 当参数相同,纯函数返回的结果一定相同,纯函数的返回值只能由传入的参数决定,不能返回与参数无关的值
>
> 如果每次调用都返回随机数,或者在reducer里面调用ajax请求都是不规范的
### Redux-thunk
redux 中间件,因为reducer的设计主要是纯函数,所以一些副效果处理,就得使用中间件处理了,这样就能够形成统一的规范,方便开发和维护
用途:抽离写在生命周期里面的异步请求逻辑,方便单元测试和维护,方便数据共享
- 安装配置
- ```ts
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
// 直接配置中间件即可
const store = createStore(reducer, applyMiddleware(thunk));
export default store
```
- 使用示例
```ts
// 获取列表
export const storeGetNewsList = () => (dispatch: Dispatch) => {
getNewsList({ page: 3 }).then(res => {
console.log(res, '-->>> res');
dispatch({ content: '', type: 'A' })
})
}
// 使用
componentDidMount() {
// react-thunk 扩展了 redux 使dispatch可以接收一个函数
store.dispatch(storeGetNewsList())
}
```
### React-Redux
Redux的封装
使用react-redux可以更加方便的使用redux,通过映射连接之后,可以自动监听store里面的值得变化,省去了手动写 `store.subscribe(this.storeChange)` 的麻烦,维护起来数据流行也更加清晰
- 创建容器
```tsx
import React from "react";
import ReactDOM from 'react-dom'
import store from './store'
import { Provider } from 'react-redux'
import MainPage from './pages/MainPage'
ReactDOM.render(
,
document.getElementById('app')
);
```
- 创建组件连接
```tsx
import React from "react";
import { Dispatch } from "redux";
import { connect } from 'react-redux'
import { IFState } from '../../../store/reducer'
import { clearContent } from '../../../store/actionCreators'
type P = {
content?: string;
clearContent?: () => void;
}
const CustomA = (props: P) => {
const { content, clearContent } = props
return (
CustomA Redux - store: {content}
)
}
// 映射 state
const mapState = (state: IFState) => {
return {
content: state.content
}
}
// 映射 props
const mapProps = (dispatch: Dispatch) => {
return {
clearContent() {
dispatch(clearContent('A'))
}
}
}
export default connect(mapState, mapProps)(CustomA)
```
这样就完成了react-redux的使用,感觉比直接使用redux要直观很多哈哈