目标
1-从0-1待见React项目工程架构
2-学习React技术栈:React、React-Router、Mobx、Rudex
3-硬件:win10
4-环境:node.js v12+
5-构建:webpack
初始化package.json
npm init -y
Webpack
是前端工程的构建工具
是前端资源打包器
重点:入口、出口、loader、plugin、配置本地服务
安装:
建议局部和全局都安装
cnpm i webpack -D 局部安装webpack
cnpm i webpack -g 全局安装
cnpm i webpack-cli -D 局部安装webpack-cli
cnpm i webpack-cli -g 全局安装
编写webpack.config.js文件的配置
指定打包环境
mode:'production' // development
入口
entry:{
//相对路径写法,有时候行不通
// main:'./src/main.js',
//绝对路径写法 -引入path模块
main:path.resolve(__dirname,'./src/ main.js')
}
出口
要打包到哪个文件夹里 只能用绝对路径
output:{
filename:'[name].[hash].js',
path:path.resolve(__dirname,'./dist')
}
loader
用于编译打包文件模块,将其转换成浏览器能够识别的兼容性代码
css: cnpm i style-loader -D
cnpm i css-loader -D
sass: cnpm i style-loader -D
cnpm i sass-loader -D
cnpm i node-sass -D
img: cnpm i file-loader -D
js: cnpm i babel-loader -D
cnpm i @babel/core -D
module: {
rules: [
// test - 正则匹配 .css 结尾的文件后缀
// 先使用css-loader,在使用style-loader,顺序不能变
// { test:/\.css$/,use:['style-loader','css-loader']},
// { test:/\.scss$/,use:['style-loader','css-loader','sass-loader']},
// 合并
{ test: /\.(scss|css)$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
{ test: /\.(png|svg|jpg|gif)$/, use: ['file-loader'] },
// exclude 用于屏蔽 文件
{ test: /\.js$/,exclude:/node_modules/, use: ['babel-loader'] },
],
}
resolve:配置模块如何解析
resolve: {
// 别名
alias: {
'@': path.resolve(__dirname, './src') //@符 等价于当前文件所在+src目录
}
}
plugin 插件
用于打包时的额外功能
html-webpack-plugin 把打包成功的js文件自动插入到一个HTML模板中去
clean-webpack-plugin 打包前先把dist目录先删除在打包
plugins:[
new HtmlWebpackPlugin({
//title要生效 标题设置 <%= htmlWebpackPlugin.options.title %>
title:'2020',
template:path.resolve(__dirname,'./public/index.html')
}),
new CleanWebpackPlugin(), //默认删除dist目录
]
配置本地服务
config.devServer = {
port:8000,
contentBase:path.resolve(__dirname,'./public'),
open:true,
hot:true, // 开启热更新 -只对main.js以后的模块起作用
}
生产环境与开发环境区分
cnpm i cross-env -D
cross-env 使用这个包来指定 process.env.NODE_ENV 环境变量
package.json文件配置
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
"serve": "cross-env NODE_ENV=development webpack-dev-server",
"start": "npm run serve"
}
webpack.config.js文件配置
var env = process.env.NODE.ENV
//配置开发环境 -- 比生产环境多配置,拎出来
if(env == 'development'){
//对象-通过.运算符添加配置
config.mode = 'development'
config.devServer = {...}
}
打包
命令行输入 webpack
webpack --config webpack.config.js
在package.json配置文件的scripts中配置
“build”:“webpack --config webpack.config.js”
命令行- npm run build
ESlint
安装: cnpm i eslint-loader -D
cnpm i eslint -D
// webpack.config.js - loader
config.module.rules.push({
test:/\.js$/,
exclude:/node_modules/, //忽略
use:['eslint-loader'],
enforce:'pre' //设置为在babel转译js代码前检测代码
})
根目录下创建 .eslintrc.json - 用于配置eslint
{
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"semi": "off" // 分号检测
// "no-console":2 规则-不允许有console.log()
// "no-multi-spaces":"error" //不允许多个空行
}
}
在 DevServer中配置 - overlay
overlay: { //出现编译器错误或警告时,在浏览器中提示全屏覆盖
// warnings: true,
errors: true
}
Babel
是js的编译器 解析为兼容性的js代码
使用React:
安装React cnpm i react -S
cnpm i react-dom -S --渲染真实dom
cnpm @babel/preset-react -D
添加一个配置文件 .babelrc.json
{
"presets": ["@babel/preset-react","@babel/preset-env"]
}
为了使用js新语法
cnpm i @babel/preset-env -D
添加根组件 App.js
//App.js 文件
import React from 'react'
export default class App extends React.Component {
constructor(props) {
super(props)
// 声明式数据 单向数据流 数据变化 => 视图变化
this.state = {
}
}
render() {
return (
<div>
<h2>{this.state.msg}</h2>
</div>
)
}
}
// main.js文件
import React from 'react'
import App from './App'
// 把react组件渲染到真实dom上
import ReactDOM from 'react-dom'
ReactDOM.render(<App />,document.getElementById('root'))
React
jsx
jsx = JavaScript + xml --语法糖
jsx - 变量、对象
jsx 非强制使用 jsx的代码更具可阅读性
jsx 可以嵌套
jsx 中可以使用表达式 - { 表达式 }
react中创建组件的方法
通过this实例访问的为 实例属性、方法
通过类名访问的为 类属性、方法
1-React.createElement() – ES5
2-clase User extends React.Component
export default class Home extends React.Component{
constructor(props){
super(props)
this.state = {}
}
render(){
return ( //jsx变量
<div>
<h1>{ 1+1 }</h1>
</div>
)
}
}
3-无状态组件 function User(props){}
– 没有state – 表示状态
const Child = (props)=>{
console.log('props',props)
return(
<div>
<h1>user 子组件</h1>
<span>{props.aaa}</span>
<span>{props.bbb}</span>
</div>
)
}
4-高阶组件 function Hoc(child){}
5-Hook 组件
生命周期
挂载阶段(Mounting)
当组件实例被创建并将其插入 DOM 时,将按以下顺序调用这些方法
····constructor – 构造器
不能使用setState()
不能把this.prop赋值给state
static getDerivedStateFromProps() – 不常用
静态的生命周期
当组件的状态或者是props发生变化的时候触发
必须return一个值
····componentDidMount() – 常用
dom准备就绪、动态数据都已经初始化完成
(开启定时器、开启常连、调接口)
更新阶段(Updating)
更新可以由对 props 或 state 的更改引起。当重新渲染组件时,按以下顺序调用这些方法
static getDerivedStateFromProps() – 不常用
静态的生命周期
当组件的状态或者是props发生变化的时候触发
必须return一个值(true/false)
shouldComponentUpdate() – 用于性能优化
(例:一些数据发生变化跟视图无关,变化时不希望更新,使用此钩子函数)
Diff运算的开关–必须return一个值(true-更新/false-不更新)
getSnapshotBeforeUpdate()
必须配合componentDidUpdate()
必须返回一个对象
卸载阶段(UNmounting)
componentWillUnmount()
当一个组件从 DOM 中删除时,将调用此方法
事件
重点: 绑事件
获取事件对象
事件传参
绑定事件有三种方式
1-bind
onClick={this.click1.bind(this,‘aaa’)}
2-箭头函数
onClick={(e) => this.click2(‘bbb’,e)}
3-变量
onClick={this.click3}
this.click3 = this.click3.bind(this,‘ccc’)
export default class User extends React.Component {
constructor(props) {
// props是父子组件的通行纽带 并且只读
super(props)
// state - 状态
this.state = {
msg: 'hello-bbb'
}
this.click3 = this.click3.bind(this,'ccc')
}
click1(arg,e) {
console.log('click1',arg, this, e)
}
click2(arg,e) {
console.log('click2',arg, this, e)
}
click3(arg,e) {
// e - 事件对象
console.log('click3',arg, this, e)
}
msgHandle() {
// 改变state 异步操作
this.setState({
msg: 'hello-修改后'
}, function () {
console.log('修改成功')
})
}
render() {
return (
<div>
{/* 绑定事件 */}
{/* bind的方式可以直接拿到事件对象,为最后一个对象 */}
<button onClick={this.click1.bind(this,'aaa')}>点击1</button>
<button onClick={(e) => this.click2('bbb',e)}>点击2</button>
{/* 不建议写法 */}
<button onClick={this.click3}>点击3</button>
<Child aaa='hello-aaa' bbb={this.state.msg}></Child>
<div>
<button onClick={this.msgHandle.bind(this)}>修改</button>
</div>
</div>
)
}
}
条件渲染
{bol && 'hello react'}
列表渲染
常用:
this.state = {
arr: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'name2' },
{ id: 3, name: 'name3' },
{ id: 4, name: 'name4' }
]
}
initList() {
// 第二种渲染方法
let { arr } = this.state
let res = []
arr.map(ele => {
res.push(
<div key={ele.id}>
<span>{ele.id}</span>
<span>-</span>
<span>{ele.name}</span>
</div>
)
})
return res
}
render(){
return(
<div>
{this.initList()}
</div>
)
}
状态提升
同一个数据的变化需要几个不同的组件来反映。我们建议提升共享的状态到它们最近的祖先组件中
// 两个子组件都需要role数据、和onRoleChange方法
return(
<div>
<h1>状态提升-组件通信</h1>
<Child1 role={role} onRoleChange={this.childFn.bind(this)}></Child1>
<Child2 role={role} onRoleChange={this.childFn.bind(this)}></Child2>
<button onClick={this.changRole.bind(this)}>修改</button>
</div>
)
组合
把同一类型组件定义在一个文件中,把需要的组件引入进行组合
import {
Model,
ModelTitle1,
ModelCon1,
ModelBtn1,
} from '@/components'
render() {
return (
<div>
<h1>组合</h1>
<Model
tiile={<ModelTitle1/>}
button={<ModelBtn1/>}>
{/* 会当做jsx变量传值到model组件中,用props接收 */}
<ModelCon1></ModelCon1>
</Model>
<hr/>
</div>
)
}
碎片
render(){
return(
<React.Fragment>
{/* 放置多个组件 */}
</React.Fragment>
// 简写
// <></>
)
}
上下文
//APP
return (
<div>
<ThemeContext.Provider value={theme.dark}>
<Context></Context>
</ThemeContext.Provider>
</div>
)
//COntext组件
// 上下文
import React from 'react'
import { ThemeContext } from '@/utils/theme'
export default class context extends React.Component {
render() {
console.log('ctx', this.context)
let ctx = this.context
return(
<div style={{color: ctx.color, background: ctx.background}}>
<h1>测试上下文</h1>
</div>
)
}
}
context.contextType = ThemeContext
//theme
import React from 'react'
// 创建上下文变量 --可以创建多个
let ThemeContext = React.createContext({})
let theme = {
dark:{
color:'white',
background:'black'
},
light:{
color:'blue',
background:'white'
}
}
export {ThemeContext,theme}
高阶组件
就是一个函数,对传入的组件进行处理,是这些组件具有同样的样式、功能等
//高阶函数
import React from 'react'
// 高阶组件就是一个函数(纯函数)
// 参数 组件(第一个参数必须是组件)
export default function hoc(WrappedComponent){
return class extends React.Component{
constructor(props){
super(props)
this.state = {
msg:'helle-hoc',
hocArr:[
{id:1,label:'hoc-1'},
{id:2,label:'hoc-2'},
{id:3,label:'hoc-3'},
]
}
}
componentDidMount(){
console.log('componentDidMount')
}
click(){
console.log('hoc-click')
}
createList(){
let {hocArr} = this.state
return hocArr.map(ele=>(
<div key={ele.id}>{ele.label}</div>
))
}
render(){
return(
<div>
<h2>高阶组件-header-hoc</h2>
<WrappedComponent
msg={this.state.msg}
onTest={this.click.bind(this)}
onInit={this.createList.bind(this)}>
<h3>高阶函数-h3</h3>
</WrappedComponent>
<h2>高阶组件-footer-hoc</h2>
</div>
)
}
}
}
//被传入至 高阶函数的组件
import React from 'react'
import hoc from '@/utils/hoc.js'
class Hoc extends React.Component{
render(){
console.log(this.props)
return(
<div>
{this.props.children}
<h1>被传入高阶函数的组件</h1>
{this.props.msg}
{this.props.onInit()}
<button onClick={this.props.onTest.bind(this)}>点击</button>
</div>
)
}
}
export default hoc(Hoc)
类型检测
安装插件 cnpm i props-type -S
import React from 'react'
import PropType from 'prop-types'
const Child = (props)=>{
// console.log('props',props)
return(
<div>
<h1>user-子组件-Child</h1>
<span>{props.aaa}</span>
<span>{props.bbb}</span>
</div>
)
}
// 检测数据类型
Child.propType = {
//类型 array bool func number object string symbol 都是小写
//必填 isRequired
aaa:PropType.string.isRequired, // string类型-必填
bbb:PropType.number //number类型
}
// 先检测再抛出
export default Child
Hooks
它是一组函数API
useState 让函数式组件拥有state
userEffect 解决函数式没有生命周期
相当于 componentDidMount mounted
componentDidUpdate updated
componentWillUnmount
beforeDestroyed
useContext useRef…
自定义Hooks
解决问题 类组件 函数组件(无状态组件)
函数式组件性能更好,但是没有生命周期、state
Hooks 用于弥补,让函数式组件拥有state、什么周期等
import React,{useState,useEffect} from "react"
export default function TestHook(props){
// 定义了一个初始值为100的声明式变量count
// setCount相当于 this.setState({})
var [ count,setCount ] = useState(100)
var [ msg,setMsg ] = useState(20)
// var [ list,setList ] = useState([])
var timer = null
function msgChange(){
setMsg(msg++)
}
// 参数 第一个参数必须是函数 且 有返回值
// 第二个参数一变化就会调用useEffect
useEffect(()=>{
// 调接口、开启定时器、开启长连接、做数据处理等 要在return里面关闭定时器、长连接
console.log('effect')
timer = setInterval(()=>{
setCount(count++)
},1000)
// 要return
// return undefined
return ()=>{
clearInterval(timer)
}
},[msg]) //msg 变化的时候就执行 useEffect
return(
<div>
<h1>Hooks测试</h1>
<h3>{count}</h3>
<h3>{msg}</h3>
<button onClick={msgChange}>加</button>
</div>
)
}
路由
运行在浏览器中安装 cnpm i react-router-dom -S
运行在React Native react-router-native
Both of react-router
分类 BrowserRouter 浏览器默认
HashRouter 哈希路由
NavLink 相当于 router-link 外面可以包一个div
Route 视图容器 相当于 router-view
外层跟Switch是直接父子关系,中间不能有其他元素包裹 -外面不能包div
exact属性 默认true 精准匹配
false 完全匹配
Switch 包裹的Route后 只匹配第一条
保证匹配关系只有一条成立
//APP 引入
import {
HashRouter,
BrowserRouter,
NavLink,
Route,
Switch,
Redirect
} from 'react-router-dom'
...
return(
<HashRouter>
<NavLink to='/list'>列表</NavLink>
<NavLink to='/hoc'>高阶</NavLink>
<NavLink to='/form'>表单</NavLink>
<Switch>
<Route path='/list' component={List}></Route>
<Route path='/hoc' component={Hoc}></Route>
<Route path='/form' component={Form}></Route>
{/* 重定向 - 放在最后 */}
<Redirect from='/*' to=''></Redirect>
</Switch>
</HashRouter>
)
通过列表循环实现
withRouter
没有被Route包裹的组件(例如components目录下的组件)没办法通过this.prop.history获取浏览器地址栏
解决:使用withRouter() 是一个高阶函数,作用是让那些没有被Route包裹的组件拥有this.props.history等API
import withRoute from 'reate-route-dom'
export default widthRoute('组件')
状态管理
mobx
– 写法灵活,适合较小型项目
安装mobx 和 mobx-react
cnpm i mobx -S
cnpm i mobx-react -S
安装babel解析插件(eslint会语法检查):
cnpm i @babel/plugin-proposal-decorators -D
cnpm i @babel/plugin-proposal-class-properties -D
配置babel文件
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
使用
1-创建Store根实例
# /store/index.js
import TodoStore from './modules/todo'
import Cnode from './modules/conde'
class Store{
constructor(){
this.todo = new TodoStore()
this.cnode = new Cnode()
}
}
// 抛出实例
export default new Store()
2-创建 子store
装饰器 observable 将其转换成可观察的
action 标记出动作所在的位置
computed 计算属性
autorun 变量发生变化跟着变化(暂时没有装饰器语法)
状态管理工具初始化的时候也会自动运行
import { observable,action, computed,autorun } from 'mobx'
export default class TodoStore{
// 共享的数据
@observable count = 1
@observable list = []
// 改变共享数据的方法用装饰器装饰
@action addCount(payload){
if(payload == 'add'){
this.count++
}else{
this.count--
}
}
// 计算属性
@computed get length(){
return this.list.length
}
// autorun
getList = autorun(()=>{
let params = {
page:1,
task:'',
limit:5
}
fetchCnodeList(params).then(res => {
console.log(res)
this.list = res
})
})
}
3-使用store
# App.js
import store from '@/store'
import { Provider } from 'mobx-react'
<Provider homeStore={store}></Provider>
# Home.js
import { observer, inject } from 'mobx-react'
@inject('homeStore')
@observer
class Home extends React.Component {
# 在 this.props.homeStore 中包含TodoList的数据和方法
}
懒加载-代码分割
库 Loadable Components
React Loadable
安装 cnpm i @babel/plugin-syntax-dynamic-import -D
cnpm i @loadable/component -S
cnpm i babel-eslint -D (把高版本的语法转换成eslint读得懂的代码)
配置 .babelrc.json文件
"plugins": ["@babel/plugin-syntax-dynamic-import"]
配置 .eslintrc.json文件
"parser": "babel-eslint"
使用
import loadable from '@/loadable/component'
const Jsx = loadable(()=>('./study/jsx'))