React---React表单与事件,生命周期,路由(v5版本以下,类组件的用法,已被淘汰)

42 阅读9分钟

React表单与事件

1、React表单组件

在HTML中,表单元素与其他元素最大的不同是它自带值或数据,而且在我们的应用中,只要是有表单出现的地方,就会有用户输入,就会有表单事件触发,就会涉及的数据处理。

在我们用React开发应用时,为了更好地管理应用中的数据,响应用户的输入,编写组件的时候呢,我们就会运用到受控组件与非受控组件这两个概念。

React推荐我们在绝大多数情况下都使用受控组件。这样可以保证表单的数据在组件的state管理之下,而不是各自独立保有各自的数据。

(1)受控组件与非受控组件

大致可分为两类:

  • 受控组件:一般涉及到表单元素时我们才会使用这种分类方法。受控组件的值由props或state传入,用户在元素上交互或输入内容会引起应用state的改变。在state改变之后重新渲染组件,我们才能在页面中看到元素中值的变化,假如组件没有绑定事件处理函数改变state,用户的输入是不会起到任何效果的,这也就是“受控”的含义所在。
  • 非受控组件:类似于传统的DOM表单控件,用户输入不会直接引起应用state的变化,我们也不会直接为非受控组件传入值。想要获取非受控组件,我们需要使用一个特殊的ref属性,同样也可以使用defaultValue属性来为其指定一次性的默认值。

受控组件简单来说就是它的值由React进行管理,而非受控组件的值则由原生DOM进行管理。示例如下:

//受控组件(省略部分代码)
<input type='text' value={this.state.value} onChange={this.handleChange}/>
​
handleChange(event) {
    this.setState({value: event.target.value});
}
​
​
//非受控组件
<input type="text" defaultValue="hello!"/>
(2)更多受控组件

在组件中声明表单元素(input_type/checked textarea select ……)时,一般都要为表单元素传入应用状态中的值,我们需要根据表单元素中用户的输入,对应用数据进行相应的操作和改变。

class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            gender: ''
        };
        this.handleGender = this.handleGender.bind(this);
    }
    handleGender(event) {
        this.setState({gender: event.target.value});
    }
​
    render() {
        return (
            <div>
                <h2>input_checked 受控组件</h2>
                <input type='radio' value="man" checked={this.state.gender=='man'} onChange={this.handleGender}/><br />
                <input type='radio' value="woman" checked={this.state.gender=='woman'} onChange={this.handleGender}/><br />
                <div>{this.state.gender}</div>
                
            </div>
        );
    }
}
​
ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

2、React事件

(1)事件类型

使用React元素处理事件与处理DOM元素上的事件非常相似。不过有一些语法上的差异:

  • React事件使用小驼峰命名法,而不是全部小写命名。
  • React事件使用JSX传递一个函数作为事件处理程序,而不是一个字符串。

示例:

  • 鼠标事件:onClick onDoubleClick onMouseDown
  • 触摸事件:onTouchStart onTouchMove onTouchEnd
  • 键盘事件:onKeyDown
  • 剪切事件:onCopy onCut onPaste
  • 表单事件:onChange onInput onSubmit
  • 焦点事件:onFocus
  • UI事件:onScroll
  • 滚动事件:onWheel
(2)事件对象
  • React对原生的事件系统也进行了封装,在React中的事件对象实际上是一个跨浏览器的虚拟事件对象 ,它拥有和原生事件对象相同的属性和方法。
  • 在react 中使用“return false” 无法阻止默认事件,只能通过事件对象调用“event.preventDefault() ”进行阻止
class ClickEvent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 'hello world'
        };
        this.changeData = this.changeData.bind(this);
    }
​
    changeData(event) {
        console.log(event)
        this.setState({value: '萨瓦迪卡'});
    }
​
    render() {
        return (
            <div>
                <button onClick={this.changeData}>点击改变</button>
                <h2>{this.state.value}</h2>
            </div>
        );
    }
}
(3)事件绑定

事件处理函数内部使用this 关键词时其指向需要注意:

class ClickEvent extends React.Component {
​
    changeData() {
        console.log(this);//undefined
    }
​
    render() {
        return (
            <div>
                <button onClick={this.changeData}>点击改变</button>
            </div>
        );
    }
}

解决方法:

  • constructor 内部对事件处理函数bind 绑定this(官方推荐)

  • 每次事件绑定都对事件处理函数做bind 绑定

  • 定义事件处理函数时使用箭头函数

    <button onClick={e=>this.changeData.call(this)}>点击改变</button>
    

当事件处理函数需要传参时:

<button onClick={this.changeData.bind(this,id)}>点击改变</button>
<p>{this.state.id}</p>

React组件生命周期

1、组件的生命周期

  • Mounting:已插入真实 DOM

  • Updating:正在被重新渲染

  • Unmounting:已移出真实 DOM

  • 组件创建阶段: 一辈子只执行一次 componentWillMount: render: componentDidMount:

  • 组件运行阶段: 按需,根据props 属性或state 状态的改变,有选择性的执行0 到多次 componentWillReceiveProps: shouldComponentUpdate: componentWillUpdate: render: componentDidUpdate:

  • 组件销毁阶段: 一辈子只执行一次

2、生命周期钩子详解

定义:在特定的阶段,ne你刚刚自动执行的函数(方法)。

  • componentWillMount :在渲染前调用,在客户端也在服务端。
  • componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。
  • componentWillReceiveProps :在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
  • shouldComponentUpdate :返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。
  • componentWillUpdate:在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
  • componentDidUpdate :在组件完成更新后立即调用。在初始化时不会被调用。
  • componentWillUnmount: 在组件从 DOM 中移除的时候立刻被调用。
class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            date: new Date()
        };
    }
    //通过componentDidMount 方法设置一个定时器,每隔1秒重新设置时间,并重新渲染:
    componentDidMount() {
        var oThis=this;
        clearInterval(this.timer);
​
        this.timer=setInterval(function() {
            oThis.setState({
                date: new Date()
            })
        }, 1000)
    }
    
    render(){
        return (
            <h2>{this.state.date.toLocaleTimeString()}</h2>
        );
    }
}

父组件的状态传递到子组件的属性中

class Content extends React.Component {
    //在渲染前调用,在客户端也在服务端
    componentWillMount() {
        console.log('Component WILL MOUNT!')
    }
    //在第一次渲染后调用,只在客户端
    componentDidMount() {
       console.log('Component DID MOUNT!')
    }
    //在组件接收到一个新的 prop (更新后)时被调用
    componentWillReceiveProps(newProps) {
        console.log('Component WILL RECEIVE PROPS!')
    }
    //在组件接收到新的props或者state时被调用
    shouldComponentUpdate(newProps, newState) {
        return true;
    }
    //在组件接收到新的props或者state但还没有render时被调用
    componentWillUpdate(nextProps, nextState) {
        console.log('Component WILL UPDATE!');
    }
    //在组件完成更新后立即调用
    componentDidUpdate(prevProps, prevState) {
        console.log('Component DID UPDATE!')
    }
    //在组件从 DOM 中移除的时候立刻被调用
    componentWillUnmount() {
         console.log('Component WILL UNMOUNT!')
    }
  
    render() {
        return (
            <div>
                <h3>{this.props.myNumber}</h3>
            </div>
        );
    }
}
​
class MyApp extends React.Component {
    constructor(props) {
        super(props);
        //声明状态
        this.state = {
            data: 0,
            isRender: true
        };
        
        this.setNewNumber = this.setNewNumber.bind(this);
        this.deleteDOM = this.deleteDOM.bind(this);
    }
    //改变状态值并传递到子组件
    setNewNumber() {
        this.setState({data: this.state.data + 1});
    }
    //删除子组件
    deleteDOM() {
        this.setState({isRender: false})
    }
    
    render() {
        return (
            <div>
                <button onClick={this.setNewNumber}>点我改变状态</button>
                <button onClick={this.deleteDOM}>点我删除子组件</button>
                { this.state.isRender ? <Content myNumber={this.state.data} /> : null }
            </div>
        );
    }
}
​
ReactDOM.render(
    <div>
        <MyApp />
    </div>,
    document.getElementById('root')
);

路由

(1)简介

React构建的是单页面应用,使用路由实现页面跳转。通过管理 URL,实现组件的切换和状态的变化

官网:reacttraining.com/react-route…

  • react-router: 实现了路由的核心功能,提供了router的核心api。如Router、Route、Switch等,但没有提供有关dom操作进行路由跳转的api;
  • react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能,例如:Link组件,会渲染一个a标签。路由模式分为:BrowserRouter(history)(不带#号)和HashRouter(带#号)。原理--history使用pushState和popstate事件构建路由;hash使用window.location.hash和hashchange事件构建路由。可以通过dom操作触发事件控制路由。

(2)基本使用

  • 下载 react-router-dom

    cnpm i react-router-dom --save
    
  • 相关组件使用

    • BrowserRouter/HashRouter:

      • 是一个路由容器,所有的路由组件(Route)、导航链接(Link)都要放置在该组件内部。
      • import 导入时使用as可以设置一个别名。
      • BrowserRouter没有#,HashRouter有#。
    • Switch:路由块,只要匹配到一个地址不往下匹配,相当于for循环里面的break

  • Route:路由规则,用于匹配路由组件

  • exact : Route属性,精确匹配路由

  • Link:导航链接,相当于a标签

  • 使用步骤

    • 第一步,规则定义:router.js文件配置路由
    import React from 'react';
    import {HashRouter as Router, Route} from "react-router-dom";
    import IndexPage from "./pages/IndexPage";
    import NewsPage from "./pages/NewsPage";
    ​
    class MyRouter extends React.Component {
      render() {
          return (
            <div>
              {/*路由容器,所有的Route 与Link 组件都要放置其内部*/}
              <Router>
                {/*这里需要一个容器,内部存放多个路由*/}
                <div> 
                    {/*方法1:复杂路由放前边,辅助精确匹配*/}
                   <Route path="/news" component={NewsPage}></Route>
                    {/*方法2:为根路由添加精确匹配*/}
                   <Route path="/" exact component={IndexPage}></Route>
                  
                </div>
              </Router>
            </div>
        );
      }
    }
    export default MyRouter;
    
    • 第二步,页面导入 :App.js文件导入使用路由
    import React from 'react';
    import MyRouter from "../router/router";
    ​
    class App extends React.Component{
      render(){
        return (
          <div>
              {/*使用路由规则*/}
              <MyRouter></MyRouter>
          </div>
        )
      }
    }
    ​
    export default App;
    
    • 第三步,创建链接标签 :IndexPage.js
    import {Link} from "react-router-dom";
    import MyRouter from "../router/router";
    ​
    class IndexPage extends React.Component {
        render() {
            return (
                <div>
                   首页
                   <Link to="/">首面</Link>
                   <Link to="/news">新闻页面</Link>
                </div>
              );
        }
      
    }
    export default IndexPage;
    
  • 另一种,组件规则的定义方式,参考官网:reactrouter.com/web/guides/…

<Router>
    <Switch>
        <Route path="/about">
            <About />
        </Route>
        <Route path="/users">
            <Users />
        </Route>
        {/*为根路由添加精确匹配*/}
        <Route path="/" exact>
            <Home />
        </Route>
    </Switch>
    </div>
</Router>

(3)路由组件懒加载

react-loadable 实现router.js文件路由组件懒加载

cnpm i -S react-loadable

loadable.js:

/*路由懒加载(异步组件)*/
import React from 'react';
import Loadable from 'react-loadable';
 
//通用的过场组件
const LoadingComponent =(props)=>{
    return (
        <div>loading...</div>
    )
}
 
//过场组件默认采用通用的,若传入了loading,则采用传入的过场组件
export default (loader,loading = LoadingComponent)=>{
    return Loadable({
        loader,
        loading
    });
}

router.js(严格模式下会报出红色警告信息)

import React from "react";
import {BrowserRouter as Router,Switch,Link,Route} from "react-router-dom";
import loadable from "./js/loadable";
​
// 异步加载组件
const Welcom = loadable(() => import('../pages/Home'))
const UserManage = loadable(() => import('../pages/UserManage'))
const Menus = loadable(() => import('../components/Menu/Menu'))
​
//……定义/导出 路由规则

(4)路由传参

1.动态路由传参,router.js

//声明要传递的参数
<Route path="/news/detail/:id/:title" component={NewsDetailPage}></Route>

在Link 的to 属性中传递,indexPage.js

//传递参数值
<Link to="/news/detail/1/新闻详情页1">新闻详情页1</Link>
​
//在组件内部获取参数
this.props.match.params.id

以上这种写法有局限性,因为在路由后面我们不确定参数个数或丢失参数时,无法匹配任何组件;需要额外定义一个组件进行提示。

2.编程式跳转传参(导航式跳转)

  • 利用导航式路由进行query 传参,注意使用BrowerRouter 模式,防止浏览器缓存

    第一步,路由规则定义,router.js

    //声明要传递的参数
    <Route path="/news/detail" component={NewsDetailPage}></Route>
    

    第二步,通过事件处理函数进行导航,事件处理函数中this 的指向,必须为某个路由对应的组件对象

    <li onClick={()=>{
           this.props.history.push({
                pathname:"/news/detail",
                //search:"?id=1&title=新闻详情2",
                query:{
                      id:1,
                      title:'新闻详情2'
                }
            })
      }}>新闻详情2</li>
      
      //目标页面获取参数
      this.props.location.query.id  
      this.props.location.search    //不会丢失参数,但因为是字符串类型,直接使用不方便
    
    • 路由传参方式 【总结】

      1. 动态路由规则定义params 传参:在组件内部使用 this.props.match.params.id 获取参数
      2. 导航式路由query 传参:在组件内部使用 this.props.location.query.id 获取参数

(5)嵌套路由

  • 在父路由组件中,通过Switch匹配子组件:
<div>
    <button onClick={this.returnPage.bind(this)}>返回</button>
    商品详情页
    
        <ul>
            <li>
                <Link to="/goods/item">商品</Link>
            </li>
            <li>
                <Link to="/goods/review">评价</Link>
            </li>
            <li>
                <Link to="/goods/detail">详情</Link>
            </li>
            <li>
                <Link to="/goods/recommend">推荐</Link>
            </li>
        </ul>
​
        <Switch>
            <Route path="/goods/item" component={ItemPage}></Route>
            <Route path="/goods/review" component={ReviewPage}></Route>
            <Route path="/goods/detail" component={DetailPage}></Route>
            <Route path="/goods/recommend" component={RecommendPage}></Route>
        </Switch>
    
</div>

(6)路由认证

模拟路由守卫实现原理,自己制作鉴权认证组件

Redirect组件是一种重定向组件 我们可以利用它来实现路由的分流:

用户访问url=>Redirect分流=>(1.有权限:去目标路由) (2.没有权限:重定向去登录路由接口)

router.js

import React from "react"
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Loadable from "../loadable"
import OAuth from "./oAuth.jsx"
class Router extends React.Component {
 render() {
    return (<BrowserRouter>
       <Switch>
        <Route path="/home" component={Loadable(() =>import("../views/home/index.js"))}/>      
        <Route path="/login" component={Loadable(()=>import("../views/login/index.js"))}/>       
        <Route path="/register" component={Loadable(()=>import("../views/register/index.js"))}/>
        <OAuth path="/car" component={Loadable(() => import("../views/car/index.js"))}/>
        <OAuth path="/info" component={Loadable(() => import("../views/info/index.js"))}/>
        <Route path='/'component={Loadable(() => import("../views/home/index.js"))}/>        
       </Switch>
     </BrowserRouter>)
   }
}
export default Router

oAuth.js

import React from "react"
import { Redirect, Switch, Route } from "react-router-dom"
import loadable from "../loadable"
class oAuth extends React.Component {
    render() {
        console.log(this.props)
        let flag = false//假数据 代表是否登录过,真实项目是取缓存
        if (!flag) { return <Redirect to="/login"></Redirect> }
        else {
            return <Route path={this.props.path} component={this.props.component}></Route>
        }
    }
}
export default oAuth