# 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要直观很多哈哈