1-从0-1待见React项目工程架构
2-学习React技术栈:React、React-Router、Mobx、Rudex
3-硬件:win10
4-环境:node.js v12+
5-构建:webpack
npm init -y
是前端工程的构建工具
是前端资源打包器
重点:入口、出口、loader、plugin、配置本地服务
建议局部和全局都安装
cnpm i webpack -D 局部安装webpack
cnpm i webpack -g 全局安装
cnpm i webpack-cli -D 局部安装webpack-cli
cnpm i webpack-cli -g 全局安装
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')
}
用于编译打包文件模块,将其转换成浏览器能够识别的兼容性代码
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: {
// 别名
alias: {
'@': path.resolve(__dirname, './src') //@符 等价于当前文件所在+src目录
}
}
用于打包时的额外功能
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
安装: 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
}
是js的编译器 解析为兼容性的js代码
安装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 (
{this.state.msg}
)
}
}
// main.js文件
import React from 'react'
import App from './App'
// 把react组件渲染到真实dom上
import ReactDOM from 'react-dom'
ReactDOM.render( ,document.getElementById('root'))
jsx = JavaScript + xml --语法糖
jsx - 变量、对象
jsx 非强制使用 jsx的代码更具可阅读性
jsx 可以嵌套
jsx 中可以使用表达式 - { 表达式 }
通过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变量
{ 1+1 }
)
}
}
3-无状态组件 function User(props){}
– 没有state – 表示状态
const Child = (props)=>{
console.log('props',props)
return(
user 子组件
{props.aaa}
{props.bbb}
)
}
4-高阶组件 function Hoc(child){}
5-Hook 组件
当组件实例被创建并将其插入 DOM 时,将按以下顺序调用这些方法
····constructor – 构造器
不能使用setState()
不能把this.prop赋值给state
static getDerivedStateFromProps() – 不常用
静态的生命周期
当组件的状态或者是props发生变化的时候触发
必须return一个值
····componentDidMount() – 常用
dom准备就绪、动态数据都已经初始化完成
(开启定时器、开启常连、调接口)
更新可以由对 props 或 state 的更改引起。当重新渲染组件时,按以下顺序调用这些方法
static getDerivedStateFromProps() – 不常用
静态的生命周期
当组件的状态或者是props发生变化的时候触发
必须return一个值(true/false)
shouldComponentUpdate() – 用于性能优化
(例:一些数据发生变化跟视图无关,变化时不希望更新,使用此钩子函数)
Diff运算的开关–必须return一个值(true-更新/false-不更新)
getSnapshotBeforeUpdate()
必须配合componentDidUpdate()
必须返回一个对象
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 (
{/* 绑定事件 */}
{/* bind的方式可以直接拿到事件对象,为最后一个对象 */}
{/* 不建议写法 */}
)
}
}
{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(
{ele.id}
-
{ele.name}
)
})
return res
}
render(){
return(
{this.initList()}
)
}
同一个数据的变化需要几个不同的组件来反映。我们建议提升共享的状态到它们最近的祖先组件中
// 两个子组件都需要role数据、和onRoleChange方法
return(
状态提升-组件通信
)
把同一类型组件定义在一个文件中,把需要的组件引入进行组合
import {
Model,
ModelTitle1,
ModelCon1,
ModelBtn1,
} from '@/components'
render() {
return (
组合
}
button={ }>
{/* 会当做jsx变量传值到model组件中,用props接收 */}
)
}
render(){
return(
{/* 放置多个组件 */}
// 简写
// <>>
)
}
//APP
return (
)
//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(
测试上下文
)
}
}
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=>(
{ele.label}
))
}
render(){
return(
高阶组件-header-hoc
高阶函数-h3
高阶组件-footer-hoc
)
}
}
}
//被传入至 高阶函数的组件
import React from 'react'
import hoc from '@/utils/hoc.js'
class Hoc extends React.Component{
render(){
console.log(this.props)
return(
{this.props.children}
被传入高阶函数的组件
{this.props.msg}
{this.props.onInit()}
)
}
}
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(
user-子组件-Child
{props.aaa}
{props.bbb}
)
}
// 检测数据类型
Child.propType = {
//类型 array bool func number object string symbol 都是小写
//必填 isRequired
aaa:PropType.string.isRequired, // string类型-必填
bbb:PropType.number //number类型
}
// 先检测再抛出
export default Child
它是一组函数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(
Hooks测试
{count}
{msg}
)
}
运行在浏览器中安装 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(
列表
高阶
表单
{/* 重定向 - 放在最后 */}
)
通过列表循环实现
没有被Route包裹的组件(例如components目录下的组件)没办法通过this.prop.history获取浏览器地址栏
解决:使用withRouter() 是一个高阶函数,作用是让那些没有被Route包裹的组件拥有this.props.history等API
import withRoute from 'reate-route-dom'
export default widthRoute('组件')
– 写法灵活,适合较小型项目
安装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'
# 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'))