美文网首页
React个人笔记

React个人笔记

作者: Peter_2B | 来源:发表于2021-04-09 09:26 被阅读0次

JSX时createElement()方法的语法糖,jsx语法被@babel/preset-react插件编译为createElement()方法。

    js表达式 & js代码(js语句)区别:
    表达式: 一个表达式会产生一个值,可以放在任何一个需要值的地方
            1.   a  (变量);
            2.   a+b;
            3.  demo(1);
            4.  arr.map();
            5.  function test(){};
    语句:
        if(){};
        for(){};
        switch(val){ case:nnn };
    let isLoading = true;
    let result = true;
    
    // if else
    // let loadData = ()=>{
    //  if(isLoading){
    //      return <div>loading</div>
    //  }
    //  return <div>data</div>
    // }

    // 三元
    let loadData = ()=>{
        return isLoading ? (<div>loading... {result?<span>true</span>:<span>false</span>} </div>):(<div>data</div>)
    }

    // 与运算符
    // let loadData = ()=>{
    //  return isLoading && (<div>loading...</div>)
    // }

    const title = (
        <h5>条件渲染 { loadData() }</h5>
    )

    ReactDOM.render(title, document.getElementById('root'));

  state的值是一个对象 -- 组件三大核心属性之一
注意: 组件中render方法中的this是组件实例对象/实例对象/组件对象;
组件自定义的方法中this为undefined;
a: 通过.bind(this)强制绑定实例对象给自定义方法 or b: 自定义方法改成箭头函数

class Mycomponent extends React.Component{
state = {               
    isHot: true, wind:'微风',mouse:false,
}
constructor(props){ //构造器只初始化调用一次
    super(props);
    //this.state = { isHot: true, wind:'微风' };

    //2. 通过.bind(this:此时this是实例对象)来绑定changeWeather的this为实例对象,
    //并返回新函数给实例对象的changeWeather属性
    this.changeWeather = this.changeWeather.bind(this);
}

render(){ 
    console.log(this);  //实例对象
    var {isHot,wind} = this.state;
    return (
    <div>
         <span style={{background: this.state.mouse?'#ddd':'#fff', display:'none'}}> ccc </span>
         <button onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</button>
    </div>
    );
}
changeWeather = () => {
    console.log(this);  // <--没有采用解决方法的话undefined;
    this.setState({isHot:!this.state.isHot}); 
}
 }
//渲染组件到页面
ReactDOM.render(<Mycomponent/>, document.getElementById('test'));

函数式组件
function Demo(props){   //函数式组件 16.版本前只能玩props. state,refs玩不了
    console.log(this);  //undefined 因为babel编译开启了严格模式:禁止自定义函数里的this指向window;
    return <h2>函数式组件适用于简单组件,{props.name},性别:{props.sex},年龄:{props.age}</h2>
}

Demo.propTypes = {  
    name:PropTypes.string.isRequired,   //name是字符串类型并且是必传的
    age:PropTypes.number,               //age是数字类型
    speak:PropTypes.func,               //speak是函数类型
}
Demo.defaultProps = {   
    sex:'no'
}
ReactDOM.render(<Demo name="tom" age={33}/>, document.getElementById('test'));
/* 
    执行ReacDOM.render(<Demo/>)之后,React解析组件标签,找到Demo组件,
    发现组件是函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,呈现在页面上
*/

props: 每个实例对象都会有props属性,通过标签属性从组件外向组件内传递数据,并保存在props对象中 -- 组件三大核心属性之一

注意: props是只读模式,不可修改;

class Person extends React.Component{
state = { } //本身在函数中 this.props可直接调用,所以几乎会简写
// constructor(props){  
    super(props);   //如果不需要实例对象操作<Person>标签上传来的属性,可简化constructor;
    console.log(this.props);
    // state = {      isHot: true    }
    // this.changeWeather = this.changeWeather.bind(this);  //为自定义函数绑定实例对象
// }

//static 给类本身添加属性和方法
static propTypes = { //限制标签属性类型
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    speak: PropTypes.func,
    getObj: PropTypes.shape({    //特定结构对象
         color: PropTypes.string,
    })
}
static defaultProps = { //默认属性
    sex:'no'
}
render(){ 
    var {name,age,sex} = this.props;
    // this.props.name = 'mmm';   无法修改, props是只读模式
    return (
        <ul>
            <li>姓名:{name}</li>
            <li>年龄:{age+1}</li>
            <li>性别:{sex}</li>
        </ul>
    )
}
}
const obj = { name:'tom', age:11, sex:'male' }
function getSpeak(){    return 'bunanbunv' }
//渲染组件到页面
ReactDOM.render(<Person {...obj} name="jerry" age={12} speak={getSpeak} />, document.getElementById('test2'));

refs & 事件处理

class Person extends React.Component{
state = {isHot: true};
blurValue = ()=>{
    const {inp} = this.refs;
    console.log(inp.value);
}
//回调函数模式
showValue = ()=>{
    const {inp2} = this;
    console.log(inp2.value);
}
myRef = React.createRef();  //(专人专用)
getValue = ()=>{
    console.log(this.myRef.current.value);      
}

myRef2 = React.createRef();
getValue2 = ()=>{
    console.log(this.myRef2.current.value);
}
render(){ 
    const {isHot} = this.state;
    return (
        <ul>
            {/*1. string类型ref*/}
            <li> <input ref="inp" type="text" onBlur={this.blurValue}/> </li>

            {/*2. 回调函数类型ref*/}
            <li> 
                <input ref={ currentNode=>{this.inp2 = currentNode}}/>   //将当前DOM节点赋值给this.inp2;  
                <button onClick={this.showValue}>click</button>
            </li>

            {/*3. createRef类型,该容器只能专用,如多个ref需要创建多个myRef = React.createRef();*/}
            <li> 
                <input ref={ this.myRef }/> 
                <button onClick={this.getValue}>click</button>
            </li>
            <li>
                <input onBlur={this.getValue2} ref={this.myRef2}/>
            </li>
        </ul>
    )}
}
//渲染组件到页面
ReactDOM.render(<Person name="tom" />, document.getElementById('test2'));

事件处理
a. React使用的是自定义事件,而不是原生的DOM事件. 原生:onclick react: onClick --为了更好的兼容性
b. React的事件是通过事件委托给组件根元素上。 多个li冒泡给ul --为了高效
event.target得到发生事件的Dom元素对象. --发生事件的元素正好是需要操作的元素可省略; event.target指的是触发事件的元素标签

myRef = React.createRef();
getValue = ()=>{
    alert(this.myRef.current.value);
}
// myRef2 = React.createRef();
getValue2 = (event)=>{
    console.log(event.target.value);    //当发生事件的元素是需要操作的元素可省略
}
render(){ 
    const {isHot} = this.state;
    return (
    <ul>
            <li> 
                <input ref={ this.myRef }/> 
                <button onClick={this.getValue}>展示左侧数据</button> 
            </li>
            <li>
                <input onBlur={this.getValue2}/>
            </li>
    </ul>
    )
}

表单中的 受控组件 & 非受控组件
受控: 页面中表单元素; 实时setState到state中,点击提交再从state获取数据 -- 类似vue的双向绑定;
非受控: 点击提交直接从input节点中获取value值, 并且每一个输入节点都需要ref

非受控组件 受控组件 受控组件 -- 柯里化写法 受控组件 -- 非柯里化写法

生命钩子函数

17.0.0.版本前:componentDidMount(vue-mounted),componentWillUnmount(vue-destroyed), render最常用
componentDidMount: 发ajax请求,设置订阅 / 启动定时器,手动更改真实DOM
componentWillUnmount:在组件卸载前做一些收尾工作, 比如清除定时器/取消订阅等
0.组件初始化挂载流程:
   1.constructor 2.componentWillMount 3.render 4.componentDidMount

2.点击+1按钮,状态更新流程:
   1.shouldComponentUpdate 2.componentWillUpdate 3.render 4.componentDidUpdate

3.点击force+1按钮, 强制更新流程:
   1.componentWillUpdate 2.render 3.componentDidUpdate

1.父组件向子组件传值,更新流程:
  1.componentWillReceiveProps 2. shouldComponentUpdate 3.componentWillUpdate 4.render
  5.componentDidUpdate

class Count extends React.Component{
        state = { count: 0}

        constructor(props){
            super(props);
            console.log('constructor');
        }
            
        add = ()=>{
            var {count} =  this.state;
            this.setState({count: count+1})
        }
        force = ()=>{
            this.forceUpdate();
        }
        death = () =>{
            //卸载组件
            ReactDom.unmountComponentAtNode(document.getElementById('test'));
        }
        //组件将要挂载钩子
        componentWillMount(){
            console.log('componentWillMount');
        }
        //组件是否能被更新; 默认省略,并且默认为true;
        shouldComponentUpdate(){
            console.log('shouldComponentUpdate');
            return true;
        }
        componentWillUpdate(){
            console.log('componentWillUpate');
        }
        render(){
            console.log('render')
            var {count} = this.state;
            return(
                <div>
                        <h2>{count}</h2>
                        <button onClick={this.add}>+1</button> <br/>
                        <button onClick={this.force}>force+1</button> <br/>
                        <button onClick={this.death}>卸载</button> 
                </div>
            )
        }
        //组件更新完的钩子; preProps标签传递来的值; prevState:更新之前的值;
        componentDidUpdate(preProps, prevState){
            console.log('componentDidUpdate',preProps, prevState);
        }
        //组件挂载完成后钩子
        componentDidMount(){
            console.log('componentDidMount');
        }
}

ReactDOM.render(<Count setCount={20}/>, document.getElementById('test'))

class Father extends React.Component{
    state = {name: 'bents'}
    changeName = ()=>{
        this.setState({ name: 'bmw' })
    }
    render(){
        var setName = this.state.name;
        return(
        <div>
            <h3>father</h3>
            <button onClick={this.changeName}>change</button>
            <Son carName={setName} />
        </div>
        )
    }
}

class Son extends React.Component{
  //子组件初始化的时候是不调用该钩子函数的,只有在父组件传改变值的时候调用
  componentWillReceiveProps(props){
      console.log(props);
      console.log('componentWillReceiveProps');
  }
  //组件是否能被更新; 默认省略,并且默认为true;
  shouldComponentUpdate(){
      console.log('shouldComponentUpdate');
      return true;
  }
  componentWillUpdate(){
      console.log('componentWillUpate');
  }
  render(){
      console.log('son-render')
      return(
        <h5>son,它的名字是:{this.props.carName}</h5>
      )
    }
    componentDidUpdate(){
        console.log('componentDidUpdate');
    }
}
ReactDOM.render(<Father/>, document.getElementById('test'))
17.0.1版本后,警告willMount, willReceive, WillUpdate,并且将来18版本移除; 新增getSnapshotBeforeUpdate,getDerivedStateFromProps生命函数几乎不用
    static getDerivedStateFromProps(props,state){
        console.log('getDerivedStateFromProps',props,state)
        return 'ccc';
    }
    render(){
        console.log('render')
        return(
            <ul ref="list">
                {
                    this.state.newsArr.map((item,index)=>{
                        return <li key={index}>{item}</li>
                    })
                }
            </ul>
        )
    }
    //快照钩子返回值给 componentDidUpdate声明周期钩子 第三参;
    getSnapshotBeforeUpdate(){
        console.log('getSnapshotBeforeUpdate');
        return this.refs.list.scrollHeight;
    }
    //组件更新完的钩子;   
                        //1参: 组件更新前路由信息(路由信息是通过props传递给组件的; props: history(.push路由跳转), location(.pathname路由信息), match);   
                        //2参: 组件更新前 state的值;  
                        //3参: getSnapshotBeforeUpdate return的值
    componentDidUpdate(preProps, prevState, listHeight){

        console.log('当前路由信息:',  this.props.location.pathname)
        console.log('当前state:', this.state)
        console.log(listHeight);
    }
    //组件已挂载钩子
    componentDidMount(){
        console.log(this.refs.list.scrollHeight);
    }

使用脚手架创建react项目模板
1.全局安装(只需要第一次安装): npm i -g create-react-app
2.切换到想要创建的目录: create-react-app 项目名
3.npm start



// 某个组件使用,放在自身state; 某些组件使用,放在共同父组件state中
//状态在哪儿,操作状态的方法在哪儿;
  //父组件
state={
    todoList:[
        {id:0,name:'吃饭',isDone:true},
    ]
}
//全选
checkAllTodo = (bool)=>{
    const {todoList} = this.state;
    const newTodoList = todoList.map(item=>{    //返回一个新的被改变值之后的数组
           return {...item, isDone: bool}
    })
    this.setState({todoList: newTodoList});

}
clearAllDone = ()=>{
    const {todoList} = this.state;
    const newTodoList = todoList.filter(item=>{ //返回数组(符合判断条件的元素),没有返回[]
        return !item.isDone
    })
    this.setState({todoList: newTodoList});
}
render() {
    return (
        <div className="todo-container">
            <div className="todo-wrap">    
                                                              //传递的参数是父组价的一个函数
                <Footer todos={this.state.todoList} checkAllTodoList={this.checkAllTodo}/>
            </div>
        </div>
    )
}

在脚手架中默认没有安装属性类型限制: npm i prop-types; 
import PropTypes from 'prop-types'

export default class Footer extends Component {
static propTypes = {
    todos: PropTypes.array.isRequired,
    checkAllTodoList: PropTypes.func.isRequired,
    clearAllDone: PropTypes.func.isRequired,
}
//this.props 父传子
// this.props.addTodo子组件调用父组件的函数,并传递实参
changeAllChecked = (event)=>{
    this.props.checkAllTodoList(event.target.checked);
}
clear = ()=>{
    this.props.clearAllDone();
}
render() {
    const {todos} = this.props;
    let count = 0;
    for(var i of todos){
        if(i.isDone){
            count+=1;
        }
    }
    let amount = todos.length;

    return (
        <div className="todo-footer">
            <label>
                <input type="checkbox" onChange={this.changeAllChecked}  checked={count===amount && amount!==0 ?true:false}/>
            </label>
            <span>
                <span>已完成{count} </span> / 全部 {amount}
            </span>
            <button className="btn btn-danger" onClick={this.clear}>清除已完成任务</button>
        </div>
    )
}
}

消息订阅&发布

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'

export default class Search extends Component {

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})
        }
    )
}

render() {
    return (
        <section className="jumbotron">
            <h3 className="jumbotron-heading">搜索github用户</h3>
            <div>
                <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
                <button onClick={this.search}>搜索</button>
            </div>
        </section>
    )
}
}

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'

export default class List extends Component {

state = { //初始化状态
    users:[], //users初始值为数组
    isFirst:true, //是否为第一次打开页面
    isLoading:false,//标识是否处于加载中
    err:'',//存储请求相关的错误信息
} 

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

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

render() {
    const {users,isFirst,isLoading,err} = this.state
    return (
        <div className="row">
            {
                isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :
                isLoading ? <h2>Loading......</h2> :
                err ? <h2 style={{color:'red'}}>{err}</h2> :
                users.map((userObj)=>{
                    return (
                        <div key={userObj.id} className="card">
                            <a rel="noreferrer" href={userObj.html_url} target="_blank">
                                <img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/>
                            </a>
                            <p className="card-text">{userObj.login}</p>
                        </div>
                    )
                })
            }
        </div>
    )
}
}

react脚手架配置代理(解决跨域问题)

方法一:在package.json中追加如下配置

"proxy":"http://localhost:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

方法二

1.在src下创建配置文件:src/setupProxy.js
2. 编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')

module.exports = function(app) {
  app.use(
    proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
      target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
      changeOrigin: true, //控制服务器接收到的请求头中host字段的值
      /*
         changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
         changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
         changeOrigin默认值为false,但我们一般将changeOrigin值设为true
      */
      pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
    }),
    proxy('/api2', { 
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {'^/api2': ''}
    })
  )
}

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

react-router-dom单页面应用: 整个应用只有一个完整的页面,点击页面中的链接不会刷新页面,只会做页面的局部刷新。
前端路由是一套映射规则,在React中,是URL路径与组件的匹配。
脚手架默认没有安装,需npm install react-router-dom

1.导航区a标签改为Link标签, Link标签浏览器也会渲染成a标签
2.展示区Route标签进行路径匹配
3.Router包裹整个应用,一个React应用只需要使用一次: <BrowserRouter>或<HashRouter>: hash: #/home
4.一般组件:只接收标签上传的属性; 路由组件: 匹配上时接收三个固定属性history:{go,goBack,goForward,replace}, location:{pathname,search,state}, match:{params,path,url}
5.Navlink&二次封装Navlink
6.Switch
7.多级路由导致的样式丢失(未写)
8.路由的模糊匹配 & 精准匹配
9.Redirect重定向 or path="/"默认路由
10.嵌套路由
11.路由传递参数
12.编程式路由跳转(路由组件)
13.withRouter(一般组件实现前进后退)
import React, { Component } from 'react'            -----App.js
import {Link,NavLink,Switch,Redirect,Route} from 'react-router-dom'
import MyNavlink from './components/MyNavlink'  --一般组件(对Navlink二次封装)
import Header from './components/Header'    --一般组件
import Home from './components/Home'        --路由组件
import About from './components/About' 

export default class App extends Component {
render() {
    return (
        <div>
            <Header title={'ccccc'}/>
            
            <div className="main">
                
                <div>
                      {/* html中,靠<a>跳转不同的页面 */}
                      {/* <a href="./about.html">About</a> */}

                      {/* 在React中靠路由链接实现切换组件 */}
                      {/* <Link to="/about">About</Link> */}
                      {/*NavLink作用是实现高亮 <NavLink activeClassName="link-active" to="/about">About</NavLink>  还需要写.link-active的css */}

                      <MyNavlink to="/about">About</MyNavlink>
                      <MyNavlink to="/home">Home</MyNavlink>
                      <MyNavlink to="/home/a/b">Home--模糊匹配</MyNavlink>
                </div>
                
                       
                <div>
                    {/* 注册路由:方式一:  exact精确匹配: 完全一致的时候才会匹配上该组件 */}
                      <Switch>
                        <Route exact path="/" component={About}/>
                        <Route path="/about" component={About}/>
                        <Route path="/home" component={Home}/>
                        {/*栗子: /home/merbers/father 会模糊匹配上Home组件; 模糊匹配只要以path开头一致就会匹配上该组件 */}
                    </Switch>
                </div>                  


                <div>
                    {/* 注册路由:方式二   Switch: 匹配上就截止;exact精准匹配会导致无法匹配二级路由*/}
                      <Switch>
                        <Route path="/about" component={About}/>
                        <Route path="/home" component={Home}/>
                        <Redirect to="/about"/>   //当未匹配上 以上的路由,就重定向为一个默认路由
                    </Switch>
                </div>


            </div>
        </div>        
    )
}
}

import React from 'react';                     -----index.js
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'
import App from './App';

ReactDOM.render(
  <BrowserRouter>   
      <App/>
  </BrowserRouter>,
  document.getElementById('root')
);
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
                    HashRouter使用的是URL的哈希值。
2.path表现形式不一样
                    BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
                    HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
                    (1).BrowserRouter没有任何影响,因为state保存在history对象中。
                    (2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。

import React, { Component } from 'react'        ----二次封装的MyNavlink
import {NavLink} from 'react-router-dom'
export default class MyNavlink extends Component {
    render() {
        {/* 父组件调用<MyNavlink to="/about">About</MyNavlink>; this.props。childern获取标签体文本about */}
        return <NavLink activeClassName="link-active" {...this.props}></NavLink>
    }
}

import React, { Component } from 'react'                      ----Home.jsx二级路由  路由嵌套
import {Switch,Redirect,Route} from 'react-router-dom'
import MyNavlink from '../../components/MyNavlink'
import News from './News'
import Message from './Message'

export default class Home extends Component {
render() {
    return (
        <div>
            <h3>Home组件内容</h3>
            <ul>
                <li>  <MyNavlink to="/home/news">News</MyNavlink>         </li>
                <li>  <MyNavlink replace to="/home/message">Message</MyNavlink>   </li>
            </ul>

            <Switch>
                <Route path="/home/news" component={News}/>
                <Route path="/home/message" component={Message}/>
                <Redirect to="/home/news"/>
            </Switch>
        </div>
    )
}
}

import React, { Component } from 'react'            想路由组件传递params参数并接收
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
state={
    messageArr:[
        {id:1,title:'message1'},
        {id:2,title:'message2'},
        {id:3,title:'message3'},
    ]
}
back= ()=>{
    this.props.history.goBack();
    this.props.history.goForward();
    this.props.history.go(1);
}
replaceShow = (id,title)=>{
    this.props.history.push('/home);
    // params 编程式路由跳转
    this.props.history.replace(`/home/message/detail/${id}/${title}`);
    // search 编程式路由跳转
    this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`);
    // state 编程式路由跳转
    this.props.history.replace(`/home/message/detail`,{id,title});
}
render() {
    let {messageArr} = this.state;
    return (
        <div>
            <ul>
                {
                    messageArr.map((item=>{
                        return (
                        <li key={item.id}>
                            {/* params 方案 */}
                            {/* <Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link> */}

                            {/*search 方案 */}
                            {/* <Link to={`/home/message/detail?id=${item.id}&title=${item.title}`}>{item.title}</Link> */}

                            {/* state 方案 */}
                            <Link replace to={{pathname:'/home/message/detail',state:{id:item.id,title:item.title}}}>{item.title}</Link>
                        </li>
                        <li>
                            <button onClick={()=>{this.pushShow(item.id,item.title)}}>push</button>
                            <button onClick={()=>{this.replaceShow(item.id,item.title)}}>replace</button>
                        </li>
                        <li>
                            <button onClick={this.back}>back</button>
                        </li>
                        )
                    }))
                }
            </ul>


            <hr/>
            {/*params方案  this.props.match.params; 接收参数*/}
            <Route path="/home/message/detail/:id/:title" component={Detail}/>
            
            {/*search 方案; import qs from 'querystring'; this.props.location; let {id} = qs.parse(search.slice(1))*/}
            <Route path="/home/message/detail" component={Detail}/>
            
            {/*state 方案 this.props.location.state; HashRouter刷新后会导致路由state参数的丢失!!!*/}
            <Route path="/home/message/detail" component={Detail}/>
        </div>
    )
}
}

import React, { Component } from 'react'      -------header.jsx
import {withRouter} from 'react-router-dom'

class Header extends Component {

back= ()=>{
    this.props.history.go(1);
    this.props.history.goBack();
    this.props.history.goForward();
}
render() {
    return (
        <div className="head">
            <h2>React Router Demo</h2>
            <button onClick={this.back}>back</button>
        </div>
    )
}
}
export default withRouter(Header)  //withRouter接收一个组件并返回一个新组件,新组件带有history,location,match方法

redux

是专门于用作状态管理的JS库(不是react插件库), 集中式管理react应用中多个组件共享的状态。
应用场景: 某个组件的状态,需要让其他组件共享。一个组价需要改变(通信)另外一个组件的状态;


action: 动作的对象。
-type: 标识属性,值为字符串,唯一必要属性。
-data: 数据属性,值类型任意,可选属性。
reducer: 用于初始化状态、加工状态,根据旧的state&action,产生新的state的纯函数。
store: 将state、action、reducer联系在一起的对象
用于定义action对象中type类型的常量值                        ----constant.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';

//引入createStore, 专门用于创建redux中最为核心的store对象     ----store.js
import {createStore,applyMiddleware,combineReducers} from 'redux'
import countReducer from './count_reducer' 
import personReducer from './person_reducer'

import thunk from 'redux-thunk'          ---异步action需引入该插件并引入applyMiddleware
const allReducer = combineReducers({
    sum:countReducer,
    people:personReducer
})
export default createStore(allReducer, applyMiddleware(thunk));

reducer函数会接到两个参数,分别为: preState(之前的状态), action(动作对象)    -----reducer.js
import {INCREMENT,DECREMENT} from './constant'

let initState = 0;
export default function countReducer(preState = initState,action){
    const {type,data} = action;
    switch(type){
      case INCREMENT:
          return preState + data;
      case DECREMENT:
          return preState - data;
      default: 
        return preState;
    }
}

//该文件专为组价返回生成返回 action对象!!                  ----action.js
import {INCREMENT,DECREMENT} from './constant'
import store from './store';

export const createIncrementAction = data => { return {type:INCREMENT,data} }
export const createDecrementAction = data => { return {type:DECREMENT,data} }
export const createIncrementAsyncAction = (data,time) => { 
  return (dispatch)=>{
      setTimeout(() => {
          dispatch( createIncrementAction(data) );  //原本store.dispatch();因为store会调用回调函数,并传入了dispatch
      }, time);
  }
}  

import React, { Component } from 'react'                ----Count.jsx
import store from '../../redux/store'
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action'

export default class Count extends Component {

increment = ()=>{
    let getValue = this.selectNumber.value*1;
    // let {count} = this.state;
    // this.setState({count:count+getValue});          
    store.dispatch( createIncrementAction(getValue) );  //得到对象 {type:'increment',data:value*1}                                            
}
decrement = ()=>{
    let getValue = this.selectNumber.value*1;
    store.dispatch(createDecrementAction(getValue));
}
incrementAsync = ()=>{
    let getValue = this.selectNumber.value*1;
    // setTimeout(() => {
        // store.dispatch(createIncrementAction(getValue));
    // }, 500);
    store.dispatch(createIncrementAsyncAction(getValue, 500));
}
componentDidMount(){
   //检测redux中状态的变化,只要变化,就调用render
       //or   直接在index.js中检测改变,就重新调用app组件
    store.subscribe(()=>{  
        this.setState({});
    })
}
render() {
    return (
        <div>
            <h2>值为: {store.getState()}</h2>
            <select ref={c=>this.selectNumber = c}>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
            &nbsp;&nbsp;&nbsp;

            <button onClick={this.increment}>+</button> 
            <button onClick={this.decrement}>-</button>
            <button onClick={this.incrementAsync}>异步加</button>
        </div>
    )
}
}

import React from 'react';        -----or 直接在index.js中检测改变,就重新调用app组件
import ReactDOM from 'react-dom';
import App from './App'
import store from './redux/store'

ReactDOM.render(<App/>, document.getElementById('root') );

store.subscribe(()=>{
    ReactDOM.render(<App/>, document.getElementById('root') );
})

react-redux

UI组件: 不能使用任何redux的api,只负责页面的呈现、交互等。
容器组件: 负责和redux通信,将结果交给UI组件。
创建一个容器组件----react-redux的connect函数
connect( mapStateToProps映射状态, mapDispatchToProps映射操作方法 )(UI组件)

import React from 'react';                                    ----index.js
import ReactDOM from 'react-dom';
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'

//引入App组件
ReactDOM.render(
     //Provider为所有容器组件提供 store的state数据
    <Provider store={store}> 
        <App/>
    </Provider>,
    document.getElementById('root') 
);

// store.subscribe(()=>{  react-redux的connect自动检测,可以忽略不写了
//     ReactDOM.render(<App/>, document.getElementById('root') );
// })
import React, { Component } from 'react'
import {connect} from 'react-redux'
import {createIncrementAction, createAddPersonAction} from '../../redux/count_action'

// function mapStateToProps(state){
//     //mapStateToProps函数返回的是对象,对象中key传递给UI组件props--状态
//     return {count:state}
// }
// function mapDispatchToProps(dispatch){
//     //mapDispatchToProps函数返回的是对象,用于传递操作状态的方法
//     return {
//         add: val => dispatch(createIncrementAction(val)),
//     }
// }

//定义UI组件
class Count extends Component {

    increment = ()=>{
        let getValue = this.selectNumber.value*1;
        this.props.add(getValue);                                                
    }
    render() {
        return (
            <div>
                <h2>值为: {this.props.count}; 有{this.props.folks.length}人</h2>
                <select ref={c=>this.selectNumber = c}>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>

                <button onClick={this.increment}>+</button> 
                <button onClick={this.decrement}>-</button>
                <button onClick={this.incrementAsync}>异步加</button>
            </div>
        )
    }
}

//创建并暴露Count的容器组件
// export default connect(mapStateToProps, mapDispatchToProps)(Count);

//简写: 自动dispatch
export default connect( 
  state=>{return {count: state.sum, folks: state.people}}, 
  { add: createIncrementAction,  jia: createAddPersonAction })(Count);
setState

setState是异步更新数据,同一个函数多次调用setState,只执行一次render

  this.state = {count:1}

  this.setState({                            //第一种方式多次调用setState,state还是初始值。
      count: this.state.count+1
  }, ()=>{
      console.log(this.state.count)         //2    第二个参数是立即的(也是DOM节点渲染后执行)
  })
  console.log(this.state.count) //1

  this.setState((state, props)=>{  //第二种方式获取到的state就是更新后的值
      return { count: state.count+1 }
  })
项目优化 --- UI框架按需加载
项目优化 --- react-virtualized长列表优化
项目优化 --- 使用纯组件,避免不必要的更新组件
项目优化 --- 路由组件lazy (只有在访问该组件时才加载该组件内容,提高首屏加载速度)
import React, { Component,lazy,Suspense } from 'react'

//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
    import Loading from './Loading'
    const Login = lazy(()=>import('@/pages/Login'))
    
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
    <Suspense fallback={<Loading/>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>
Hook

Hook是React 16.8.0版本增加的新特性/新语法
State Hook: React.useState可以让你在函数组件中使用 state
Effect Hook: React.useEffect 可以在函数组件中模拟类组件中的生命周期钩子(componentDidMount, componentDidUpdate, componentWillUnmount)
Ref Hook: React.createRef 可以在函数组件中存储/查找组件内的标签或任意其它数据

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class Count extends Component {
    state = {clock:0,count: 0}

    add = ()=>{
        this.setState(state=>({count: state.count+1}))
    }
    showInfo = ()=>{
        let {inp} = this;
        alert(inp.value);
    }
    unMount = ()=>{
         ReactDOM.unmountComponentAtNode(document.getElementById('root'));
    }
    componentWillUnmount(){
       clearInterval(this.timer);
    }
    componentDidMount(){
        this.timer = setInterval(()=>{
            this.setState(state=>({clock: state.clock+1}))
        },1000)
    }
    render() {
        return (
            <div>
                <h4>定时器{this.state.clock}</h4>
                <h4>加{this.state.count}</h4>
                <button onClick={this.add}>+1</button>
                <button onClick={this.unMount}>卸载组件</button>
                <input type="text" ref={c=>{this.inp = c}}/>
                <button onClick={this.showInfo}>显示输入的信息</button>
            </div>
        )
    }
}

function Count(){
    let [clock, setClock] = React.useState(0);  //第1个为内部当前状态值;第2个为更新状态值的函数;useState(初始值)
    let [count, setCount] = React.useState(0);
    let [name, setName] = React.useState('tom');
    let myRef = React.useRef();

    function add(){
        setCount(count => count+1); 
        setName('jack');
    }
    function showInfo(){
        alert(myRef.current.value);
    }
    function unMount(){
        ReactDOM.unmountComponentAtNode(document.getElementById('root'));
    }
    //空数组, DidMount调用该函数
    //没有传数组 or 指定数组变量, DidMount & DidUpdate都调用该函数;
    React.useEffect(()=>{
        let timer = setInterval(() => {
            setClock(clock => clock+1); 
        }, 1000);
        return ()=>{    //return返回的函数相当于componentWillUnmount
            clearInterval(timer)
        }
    },[]);

    return (
        <div>
            <h4>定时器{clock}</h4>
            <h4>加{count}</h4>
            <h5>名字{name}</h5>
            <button onClick={add}>+1</button>
            <button onClick={unMount}>卸载组件</button>
            
            <input type="text" ref={myRef}/>
            <button onClick={showInfo}>显示输入的信息</button>
        </div>
    )
}

export default Count
Context

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信(较少用)

import React, { Component } from 'react'

const MyContext = React.createContext();
const {Provider,Consumer} = MyContext;
class A extends Component {
    state = {name:'tom',age:10}

    render() {
        let {name,age} = this.state;
        return (
            <div>
                <h5>grandpa</h5>
                <h6>名字: {name}</h6>
                <Provider value={{name,age}}>
                    <B/>
                </Provider>
            </div>
        )
    }
}

class B extends Component {

    render() {
        return (
            <div>
                <h5>parent</h5>
                <C/>
            </div>
        )
    }
}

// class C extends Component {
//     static contextType = MyContext; //声明接收context
//     render() {
//         return (
//             <div>
//                 <h5>grandchild</h5>
//                 <h6>名字: {this.context.name},年龄:{this.context.age}</h6>
//             </div>
//         )
//     }
// }
function C(){
    return (
            <div>
                <h5>grandchild</h5>
                {/* <h6>名字: {this.context.name},年龄:{this.context.age}</h6> */}
                <Consumer>
                    {
                        value=>{
                            return <h6>名字: {value.name},年龄:{value.age}</h6>
                        }
                    }
                </Consumer>
            </div>
        )
}
export default A
render-props

动态传入带内容的结构(标签), 类似Vue中slot插槽;
如果两个组件中部分功能相同,可抽离出,复用state&操作状态的方法;可使用render-props & 高阶组件(HOC)方式

import React, { Component } from 'react'

class Count extends Component {
    render() {
        return (
            <div>
                <h5>A</h5>
                <A renderC={(name)=> <B name={name}/>}/>
            </div>
        )
    }
}
class A extends Component {
    state = {name:'tom',age:10}
    render() {
        let {name} = this.state;
        return (
            <div>
                <h5>B</h5>
                {this.props.renderC(name)}  //props.renderC将state暴露到组件外部
            </div>
        )
    }
}
class B extends Component {
    render() {
        return (
            <div>
                <h5>C {this.props.name}</h5>
            </div>
        )
    }
}
export default Count
import React, { Component } from 'react'

import img from '../../assets/pi.jpg'
class CC extends Component {
    render() {
        return (
            <div>
                {/* <Mouse renderFn={(mouse)=>{
                    return <p>鼠标位置: {mouse.x}--{mouse.y}</p>
                }}/> */}
                <Mouse>
                {(mouse)=>{
                    return <p>鼠标位置: {mouse.x}--{mouse.y}</p>
                }}
                </Mouse>

                {/* <Mouse renderFn={(mouse)=>{
                    return <img src={img} alt="cat" style={{position:'absolute',top:mouse.y,left:mouse.x,width:'100px'}}/>
                }}/> */}
            </div>
        )
    }
}

class Mouse extends Component {
    state = {x:0, y:0};
    
    handleMouseMove = (e)=>{
        this.setState({
            x: e.clientX, y: e.clientY
        })
    }
    componentDidMount(){
        window.addEventListener('mousemove', this.handleMouseMove)
    }
    componentWillUnmount(){
        window.removeEventListener('mousemove', this.handleMouseMove)
    }
    render() {
        // return this.props.renderFn(this.state)   //props.renderC将state暴露到组件外部
        return this.props.children(this.state)
    }
}
export default CC
高阶组件

是一个函数,接收要包装的组件,返回增强后的组件,高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑.

import React, { Component } from 'react'

import img from '../../assets/pi.jpg'

function withMouse(WrappedComponent){

    class Mouse extends Component {
        state = {x:0, y:0};
        
        handleMouseMove = (e)=>{
            this.setState({
                x: e.clientX, y: e.clientY
            })
        }
        componentDidMount(){
            window.addEventListener('mousemove', this.handleMouseMove)
        }
        componentWillUnmount(){
            window.removeEventListener('mousemove', this.handleMouseMove)
        }
        render() {
            console.log(this.props);
            return (<WrappedComponent {...this.state} {...this.props}></WrappedComponent>)
        }
    }
    return Mouse;
}

const Position = props => {
    return (<p>鼠标位置: {props.x}--{props.y}</p>)
}

const MousePosition = withMouse(Position);

class CC extends Component {
    render() {
        return (
            <div>
                <h4>cc</h4>
                {/* 渲染增强后的组件 */}
                <MousePosition a={1}/> 
            </div>
        )
    }
}


export default CC
错误边界

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误(只在生产模式起作用)

import React, { Component } from 'react'

class Parent extends Component {
    state = {
        hasError:'', //组件是否产生错误
    }
    //当Parent子组件出现报错时候,会触发getDerivedStateFromError调用,并懈怠错误信息
    static getDerivedStateFromError(err){
        console.log(err);
        return {hasError:err}  // 在render之前触发,返回新的state
    }
    render() {
        return (
            <div>
                <h5>A</h5>
                {this.state.hasError ? <h3>稍后再试</h3> : <Child/>}
            </div>
        )
    }
}

class Child extends Component {
    state = {
        users:'no',
        // users:[
        //     {id:1,name:'tom',age:10},
        //     {id:2,name:'jerry',age:20},
        // ]
    }
    render() {
        return (
            <div>
                <h5>Child</h5>
                {
                    this.state.users.map(item=>{
                        return <h5 key={item.id}>{item.name}</h5>
                    })
                }
            </div>
        )
    }
}

export default Parent
组件优化

1.减轻state: 只存储与组件渲染相关的数据.(如定时器timer可放在this实例对象中);

2.组件更新机制: 父组件重新渲染时,也会重新渲染子组件树.当子组件没有任何变化时不进行重新渲染,使用钩子函数shouldComponentUpdate(nextProps,nextState);

3.使用纯组件PureComponent: PureComponent内部自动实现了shouldComponentUpdate钩子,内部通过shallow compare(数据类型-浅对比)对比前后两次props和state值,来判断是否重新渲染组件。

但是对于引用类型来说,直接修改this.state.obj.name="tom",再setState({obj:this.state.obj}),就不会重新渲染组件。

import React, { Component } from 'react'

//class index extends PureComponent {
class index extends Component {
    state = {
        number:0,
        obj:{name:'tom',age:10}, 
        arr:[1,2,3]
    }

    handleClick = ()=>{
        this.setState((state)=>{
            return {
                number: Math.floor( Math.random() * 3),
                obj: {...state.obj, age:20},
                arr: [...state.arr, 4,5,6]
            }
        })
    }
    shouldComponentUpdate(nextProps, nextState){
        console.log('更新值:',nextState.number,'上一次值:',this.state.number)
        return nextState.number === this.state.number ? false:true;
    }
    render() {
        console.log('render');
        return (
            <div>
                <p>随机数: {this.state.number}</p>
                <button onClick={this.handleClick}>click</button>
            </div>
        )
    }
}

export default index
axios

npm install axios;

import React, { Component } from 'react'
import { Carousel, Flex } from 'antd-mobile';
import axios from 'axios';

import pi from '../../../assets/pi.jpg'
import './index.css'


const navs = [           //这里的数据没有放在state中,因为state是状态,一般存储的是变化的内容。
    { id:1, img: pi, title: 'txt1', path: '/home/list' },
    { id:2, img: pi, title: 'txt2', path: '/home/list' },
    { id:3, img: pi, title: 'txt3', path: '/home/list' },
]

export default class index extends Component {

        state = {
            swipers: [],
            isSwiperLoaded: false,      
        }

        async getSwipers(){
            // 1.轮播图不自动播放? 2.从其他路由返回,高度缩短?  动态加载后swipers数量不一致
            // const res = await axios.get('http://localhost:9000/home/swiper',{
            //                              params:{ id: lorem }
            //});
            this.setState({
                isSwiperLoaded: true,
                swipers: [
                    'https://zos.alipayobjects.com/rmsportal/AiyWuByWklrrUDlFignR.png', 
                    'https://zos.alipayobjects.com/rmsportal/TekJlZRVCjLFexlOCuWn.png', 
                    'https://zos.alipayobjects.com/rmsportal/IJOtIlfsYdTyaDTRVrLI.png'],
            });
        }

        componentDidMount() {
            this.getSwipers();
        }

        renderSwipers(){
            return this.state.swipers.map(item=>{

                return  <a  key={item} href="http://www.alipay.com" style={{ display: 'inline-block', width: '100%', height: 176 }}>
                            <img alt="" src={item} style={{ width: '100%', verticalAlign: 'top' }} />
                        </a>
            })
        }
        renderNavs(){
            return navs.map(item=>(
                    <Flex.Item key={item.id} onClick={()=>{ this.props.history.push(item.path) }}> 
                            <img src={item.img} />
                            <h5>{item.title}</h5>
                    </Flex.Item> 
            ))
        }
        render() {
            return (
                <div>
                   
                    <div className="swiper">   {/* <-设置默认高度避免axios数据回来后闪动 */}

                        {/* 3.通过isSwiperLoaded: true时,才渲染轮播组件 */}
                        {this.state.isSwiperLoaded ? (
                            <Carousel autoplay  infinite>
                                {this.renderSwipers()}
                                {/* {this.state.swipers.map(item => (
                                    <a  key={item} href="http://www.alipay.com" style={{ display: 'inline-block', width: '100%', height: 176 }}>
                                    <img alt="" src={item} style={{ width: '100%', verticalAlign: 'top' }} />
                                    </a>
                                ))} */}
                            </Carousel>
                        ): '' }
                        
                    </div>

                    <Flex className="nav">
                        {this.renderNavs()}
                    </Flex>
                </div>
            );
        }
}
   </div>
            );
        }
}

react-spring 动画库

install: npm install react-spring
文档: https://react-spring.io/components/spring
导入Spring组件,使用spring组件包裹要实现动画效果的遮罩层div。
通过render-props模式, 将参数props(样式)设置为遮罩层div的style。
给Spring组件添加from,to属性,组件第一次渲染时动画状态到更新的动画状态。

import React, { Component } from 'react'

import { Spring, animated } from 'react-spring'

import './index.scss'

export default class index extends Component {
    state = {
        show: false,
    }

    showBar = ()=>{
        var tempShow = this.state.show;
        this.setState({show: !tempShow})
    }

    renderAnimation = ()=>{
        var tempShow = this.state.show;        

                //from是在初始化时; 之后的变化都是根据to属性展示隐藏
        return <Spring from={{opacity: 0}} to={{opacity: tempShow? 1:0}}>
                {styles => {
                        return <animated.div  style={styles} className="animation">ccc</animated.div>
                }}
            </Spring>
    }

    render() {
        return (
            <div>
                
                <button onClick={ this.showBar }>click</button>
                
                {this.renderAnimation()}
                {/* {
                    this.state.show ? 
                        <Spring from={{opacity: 0}} to={{opacity: 1}}>
                            {styles => (          //.animation{ margin-top: 30px; background: orange; }
                                 <animated.div  style={styles} className="animation">ccc</animated.div>
                            )}
                        </Spring> : null
                } */}

            </div>
        )
    }
}
canvas-video.gif

相关文章

网友评论

      本文标题:React个人笔记

      本文链接:https://www.haomeiwen.com/subject/qybmkltx.html