React梳理归纳

React是什么?

官方解释:用于构建用户界面的Javascript库

为什么要学React?

优势

  • 采用组件化模式、声明式编码,提高开发效率及组件复用率。
  • 在React Native中可以使用Reacti吾法进行移动端开发
  • 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。

和声明式编程相对应的是命令式编程(Imperative Programming),大部分语言的hello world都是从命令式编程开始的。

声明式编程不用告诉电脑问题领域,从而避免随之而来的副作用。而指令式编程则需要用算法来明确的指出每一步该怎么做

虚拟DOM放到电脑内存里

 

原生JS三大痛点

1.操作DOM繁琐、效率低。

2.直接操作DOM浏览器会进行大量的重绘重排。

3.没有组件化编码方案,代码复用率低。

 

基础 

创建虚拟DOM (如果要嵌套的话,太过麻烦)

const VDOM = React.createElement('h1',{id:'title'},'Hello,React')

JSX的出现,解决了这个问题,可以向写HTML一样来创建虚拟DOM 

const VDOM = (
 <div>
    <span>蓝刀是我</span>   
</div>   
)

使用debugger对比虚拟DOM和真实DOM

虚拟DOM(非常的轻便) 真实DOM(非常的笨重) 

 

 XML: 早期用于存储和传输数据

<student>
    <name>Jack</name>
    <age>18</age>
</student>

后来一般用JSON代替 XML的内容比数据还多

{"name":"Jack","age":18}

花括号{} 只能插入JS表达式

【js语句(代码)】与【js表达式】区别
          1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
                下面这些都是表达式:
                    (1). a
                    (2). a+b
                    (3). demo(1)
                    (4). arr.map() 
                    (5). function test () {}
          2.语句(代码):
                下面这些都是语句(代码):
                    (1).if(){}
                    (2).for(){}
                    (3).switch(){case:xxxx}

 

  执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
          1.React解析组件标签,找到了MyComponent组件。
          2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
          3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。

 

 render中的this是组件实例对象,因为他是实例对象.render出来的

render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
类的语法,继承自组件,如果有构造器,必须有super()来帮你调用父类的构造器

 

state 存放着数据,数据改变=> 驱动着页面的展示。

changeWeather(){
  //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
  //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
  //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
  this.setState({isHot:!isHot})
}

赋值语句,相当于给实例自己加,而方法是在原型上加

//自定义方法————要用赋值语句的形式+箭头函数
  changeWeather = ()=>{
   const isHot = this.state.isHot
   this.setState({isHot:!isHot})
  }

 赋值语句,一定要写成高阶函数才不会出现一渲染就调用的情况

handleMouse = (flag) => {
    return (event) => {
        console.log(flag,event.target.checked )
    }
}

<input  type="checked " onMouseEnter={this.handleMouse(true)} >

 

state 简写方法

state = {isHot:false,wind:'微风'}

 ref 写法

官方建议,尽量少用

1.字符串(快废弃)

2.回调  

ref={(c)=>{this.input1 = c;}}

3.createRef(官方推荐)

myRef = React.createRef();
ref={this.myRef};

 

事件处理 

/* 
  (1).通过onXxx属性指定事件处理函数(注意大小写)
         a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
         b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素最终绑定到了document这个DOM节点上) ————————为了高效
  (2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
 */

合成事件 https://zhuanlan.zhihu.com/p/25883536

如果DOM上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响。React为了避免这类DOM事件滥用,同时屏蔽底层不同浏览器之间的事件系统差异,实现了一个中间层——SyntheticEvent。

  1. 当用户在为onClick添加函数时,React并没有将Click时间绑定在DOM上面。
  2. 而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装交给中间层SyntheticEvent(负责所有事件合成)
  3. 所以当事件触发的时候,对使用统一的分发函数dispatchEvent将指定函数执行。

高阶函数、函数的柯里化
saveFormData = (dataType)=>{
  return (event)=>{
    this.setState({[dataType]:event.target.value})
  }
}
<input onChange={this.saveFormData('username')} type="text" name="username"/>

 

/* 
  高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
          1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
          2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
          常见的高阶函数有:Promise、setTimeout、arr.map()等等

  函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。 
    function sum(a){
      return(b)=>{
        return (c)=>{
          return a+b+c
        }
      }
    }
*/

 

新旧生命周期

 新

 

react17开始,废弃componentWillReceiveProps  用 getDerivedStateFromProps 来代替

 罕见案例, state的值只取决props

 

react脚手架 create-react-app

index.html 文件

<!DOCTYPE html>
<html lang="en">
  <head>
        <meta charset="utf-8" />
        <!-- %PUBLIC_URL%代表public文件夹的路径 -->
        <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
        <!-- 开启理想视口,用于做移动端网页的适配 -->
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
    <meta name="theme-color" content="red" />
    <meta
      name="description"
      content="Web site created using create-react-app"
        />
        <!-- 用于指定网页添加到手机主屏幕后的图标 -->
        <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
        <!-- 应用加壳时的配置文件 -->
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
        <!-- 若llq不支持js则展示标签中的内容 -->
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
web-vitals 评估网站健康状况
 
nanoid 生成全世界唯一标识(key)的库
 

引入的原则:

第三方往上靠,自己的放中间,样式放最后

 css 模块化


import hello from './index.module.css';

className = {hello.xxx}

anfn anonymous(匿名函数)

 

强制更新(不更改任何state中的数据)

force = ()=>{
   this.forceUpdate()
}

卸载组件

death = ()=>{
  ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}

 

前端路由

withRouter

把不是通过路由切换过来的组件中,将react-router 的 history、location、match 三个对象传入props对象上

默认情况下必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,执行this.props.history.push('/detail')跳转到对应路由的页面
然而不是所有组件都直接与路由相连(通过路由跳转到此组件)的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props

设置withRouter很简单只需要两步:(1)引入  (2)将App组件 withRouter() 一下

import React,{Component} from 'react'
import {Switch,Route,NavLink,Redirect,withRouter} from 'react-router-dom' //引入withRouter
class App extends Component{
    //此时才能获取this.props,包含(history, match, location)三个对象
    console.log(this.props);  //输出{match: {…}, location: {…}, history: {…}, 等}
    render(){return (<div className='app'>
            <NavLink to='/abc'>用户列表</NavLink>
            <Switch>
                <Redirect from='/' to='/one' exact />
            </Switch>
        </div>)
    }
}
export default withRouter(App);  //这里要执行一下WithRouter

 

 使用Router包裹包裹后的路由,打包后缺失路由那段代码?

要改为hash模式才能使用

// 设置路由模式
mirror.defaults({
    historyMode: 'hash'
});

 

history有两种使用方法

  • 直接使用H5推出的history身上的API  History.createBrowserHistory()
  • hash值 History.createHashHistory()(锚点)看起来不好看,但是兼容性极强

react-router的理解  

  • 包含三种类型  1 Web (react-router-dom)2.react-native (react navigation)3.any

withRouter 使普通组件变为路由组件 location history match 属性   (location是history的一个属性)

 

ajax有三种携带请求参数的形式

1.query 

2.params

3.body(请求体)  =>  1.urlencode 2.json

路由携带参数的三种方法

1.传递参数

<li key={msgObj.id}>

  {/* 向路由组件传递params参数 */}
  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

  {/* 向路由组件传递search参数 */}
  {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}

  {/* 向路由组件传递state参数 */}
  <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>

</li>

2.注册时声明参数

{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}

{/* search参数无需声明接收,正常注册路由即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}

{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>

3.接收参数   (querystring是nodejs原生模块,也是raeact的内置库)

// 接收params参数
// const {id,title} = this.props.match.params 

// 接收search参数
// const {search} = this.props.location
// const {id,title} = qs.parse(search.slice(1))

// 接收state参数
const {id,title} = this.props.location.state || {}  // 有值就用,没值就空对象

刷新后state还在,是因为使用的是 BrowserHistory 它一直操控history 帮你存着state,清除缓存才会消失

向路由组件传递参数

1.params参数
      路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
      注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
      接收参数:this.props.match.params
2.search参数
      路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
      注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
      接收参数:this.props.location.search
      备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数
      路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
      注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
      接收参数:this.props.location.state
      备注:刷新也可以保留住参数

解决多级路径刷新页面样式丢失的问题

devServer内置3000端口服务器,请求不存在的文件,会展示public的index.html

1.public/index.html 中 引入样式时不写 ./ 写 / 直接访问根路径(常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)  react特有
3.使用HashRouter // 带了#后

路由的严格匹配与模糊匹配

1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

连续解构赋值

const a = {b:{c:{d:6}}};
const {b:{c:{d}}} = a;
console.log(d);

//连续解构赋值别名
const a = {b:{c:{d:6}}};
const {b:{c:{d:alias}}} = a;
console.log(alias);

 数组解构赋值

let day, month ,year;

const [year, month, day] = '2021/10/25';

[year,month,day] = '2021/10/25'.split('/');

 

代理

产生跨域的本质是ajax引起把服务器返回的响应拦住了,而中间人是通过请求转发的模式,同源策略不限制他,拿到数据后,因为他是相同的端口,所以客户端可以拿到数据。

 

 

setupProxy.js

// package.json中的proxy就是这个配置文件的简写
const proxy = require('http-proxy-middleware');

module.exports = function(app){
  app.use(
    proxy('/api1',{ // 遇见/api1前缀的请求,就会触发该代理配置
      target: 'http://localhost:5000', // 请求转发给谁
      changeOrigin: true,// 控制服务器收到的请求头中Host的值
      pathRewrite: {'^api1':''} //重写请求路径(必须)
    })
  )
}

 

组件交互/传值

消息订阅与发布

pubsub-js

发送fetch请求   (fetch和XMLHttpRequest同级,XMLHttpRequest是ajax的基础,axios和ajax都基于XMLHttpRequest,fetch是浏览器原生方法)

 import PubSub from 'pubsub-js'
search = ()=>{
   //获取用户的输入(连续解构赋值+重命名)
   const {keyWordElement:{value:keyWord}} = this
   //发送请求前通知List更新状态
   PubSub.publish('atguigu',{isFirst:false,isLoading:true})
   //发送网络请求
   axios.get(`/api1/search/users?q=${keyWord}`).then(
       response => {
           //请求成功后通知List更新状态
           PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
       },
       error => {
           //请求失败后通知App更新状态
           PubSub.publish('atguigu',{isLoading:false,err:error.message})
       }
    )
}
//发送网络请求---使用fetch发送(未优化)
fetch(`/api1/search/users2?q=${keyWord}`).then(
  response => {
    console.log('联系服务器成功了');
    return response.json()
  },
  error => {
    console.log('联系服务器失败了',error);
    return new Promise(()=>{})
  }
).then(
  response => {console.log('获取数据成功了',response);},
  error => {console.log('获取数据失败了',error);}
) 
//发送网络请求---使用fetch发送(优化)
try {
   const response= await fetch(`/api1/search/users2?q=${keyWord}`)
   const data = await response.json()
   console.log(data);
   PubSub.publish('atguigu',{isLoading:false,users:data.items})
 } catch (error) {
   console.log('请求出错',error);
   PubSub.publish('atguigu',{isLoading:false,err:error.message})
 }

 

接受请求

componentDidMount(){
  this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{
    this.setState(stateObj)
  })
}

componentWillUnmount(){
  PubSub.unsubscribe(this.token)
}

 

 

 

 

 

 

 

1. 事件监听

React中使用onClick类似的写法来监听事件,注意this绑定问题 react里严格遵循单项数据流,没有数据双向绑定,
所以输入框要设置value和onChange 
handleChange(e){ this.setState({ name:e.target.value }) }
<input type="text" value={this.state.name} onChange={(e)=>this.handleChange(e)} />

2.  PureComponent Vs Component  https://www.jianshu.com/p/c41bbbc20e65

3.  React.memo

React v16.6.0 之后的版本,可以使用 React.memo 让函数式的组件也有PureComponent的功能 
4. HOC(Higher-Order Components) 高阶组件
高阶组件也是一个组件,但是他返回另外一个组件,产生新的组件可以对属性进行包装,甚至重写部分生命周期 
const withKaikeba = (Component) => { const NewComponent = (props) => { return <Component {...props} name="开课吧高阶组件" />; };return NewComponent; };
上面withKaikeba组件,其实就是代理了Component,只是多传递了一个name参数 
5. 高阶组件最巧妙的一点,是可以链式调用。
import React, {Component} from 'react'
import {Button} from 'antd'
const withKaikeba = (Component) => {
    const NewComponent = (props) => {
        return <Component { ...props } name = "开课吧高阶组件" / > ;
    };
    return NewComponent;
};
const withLog = Component => {
    class NewComponent extends React.Component {
        render() {
            return <Component { ...this.props }  />;
        }
        componentDidMount() {
            console.log('didMount', this.props)
        }
    } return NewComponent
}
class App extends Component {
    render() {
        return (
            <div className="App">
                <h2>hi,{this.props.name}</h2 >
                < Button type="primary" > Button < /Button>
                </div >
             )
   }
   }
export default withKaikeba(withLog(App))
6. 高阶组件装饰器写法
npm install --save-dev babel-plugin-transform-decorators-legacy
const { injectBabelPlugin } = require("react-app-rewired");
module.exports = function override(config) {
  config = injectBabelPlugin(
    [
      "import",
      {
        libraryName: "antd",
        libraryDirectory: "es",
        style: "css"
      }
    ],
    config
  );
  config = injectBabelPlugin(
    [
      "@babel/plugin-proposal-decorators",
      {
        legacy: true
      }
    ],
    config
  );
  return config;
};

使用装饰器

import React, { Component } from 'react'
import { Button } from 'antd'
const withKaikeba = (Component) => {
    const NewComponent = (props) => {
        return <Component { ...props } name="开课吧高阶组件" />;
    };
    return NewComponent;
};
const withLog = Component => {
    class NewComponent extends React.Component {
        render() {
            return <Component {  ...this.props } />;
        }
        componentDidMount() {
            console.log(Component.name, 'didMount', this.props)
        }
    } return NewComponent
}
@withKaikeba
@withLog
class App extends Component { render() { return ( <div className="App"> <h2>hi,{this.props.name}</h2 > < Button type="primary" > Button < /Button> </div > ) } } export default App

7.  按需加载

1.安装react-app-rewired取代react-scripts,可以扩展webpack的配置 ,类似vue.config.js
npm install react-app-rewired@2.0.2-next.0 babel-plugin-import --save

 

2.根目录创建config-overrides.js 文件

const {injectBabelPlugin} = require('react-app-rewired');

module.exports = function override(config, env) {

    config = injectBabelPlugin(
        ['import', {libraryName: 'antd', libraryDirectory: 'es', style: 'css'}],
        config
    );

    config = injectBabelPlugin([
        "@babel/plugin-proposal-decorators",
        {
            "legacy": true
        }
    ], config);

    return config;

};

3.把package.json的react-script 替换为 react-app-rewired

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build && mkdir ./build/views/ && mv ./build/index.html ./build/views/",
    "test": "react-app-rewired test --env=jsdom"
  }

 8. 阻止事件冒泡

 onClick={e=>{
         e.nativeEvent.stopImmediatePropagation(); //PC
         e.stopPropagation(); // 移动
}}

9.webpack配置路径别名alias

 

 

 

 

 

 

导入组件时  提示 Module is not installed

解决方法:配置idea的js下面的webpack设置

 

修复后

 

 并且可以跳转

create-react-app 项目中,没有暴露webpack配置的情况下有两种修改别名方案:

1.根目录添加 jsconfig.json文件

{
    "compilerOptions": {
        "baseUrl": "./src"
    }
}

现在 src就是根目录了, src下的路径文件夹都可以alias

1, 安装 react-app-rewired customize-cra

    npm install react-app-rewired customize-cra --save-dev

2,在项目根目录也就是package.json平级目录,新建一个config-overrides.js文件

   并在js文件中, 书写以下代码(为@根路径配置代码)

const { override, addWebpackAlias } = require("customize-cra")

const path = require("path");

module.exports=override(

    //引入插件写相关配置

  addWebpackAlias({

    "@" : path.resolve(__dirname, "src")

  })

)

 

3,在package.json更改 命令

"scripts": {

    "dev": "react-app-rewired start",

    "build": "react-app-rewired build",

    "test": "react-app-rewired test",

    "eject": "react-scripts eject"

  },

4,js中使用 

 

这样通过yarn dev 重启项目就可以了

 

 10.react-loadable 组件切割/组件按需加载 避免打包后js文件都混在一个文件内,导致客户加载时间过长,影响体验。

 

 

 

 使用 :

 

 

 

 

posted @ 2020-01-21 00:15  一路向北√  阅读(335)  评论(0编辑  收藏  举报

web应用开发&研究 -

业精于勤而荒于嬉。

工作,使我快乐。


Font Awesome | Respond.js | Bootstrap中文网