美文网首页
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