手动搭建react环境

目标

​ 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 (
      

{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'))

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变量
			

{ 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 组件

生命周期

挂载阶段(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 (
      
{/* 绑定事件 */} {/* 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

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(
        

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(
	
		列表
        高阶
        表单
        
            
            
            
            {/* 重定向 - 放在最后 */}
            
        
	
)

通过列表循环实现

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'


# 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'))

你可能感兴趣的:(手动搭建react环境)