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 //不会丢失参数,但因为是字符串类型,直接使用不方便
-
路由传参方式 【总结】 :
- 动态路由规则定义params 传参:在组件内部使用 this.props.match.params.id 获取参数
- 导航式路由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