生命周期
- componentWillMount 在渲染前调用,在客户端也在服务端,比较少用。
- componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
- componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
- shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。可以在你确认不需要更新组件时使用。
- componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
- componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
- componentWillUnmount在组件从 DOM 中移除之前立刻被调用。
注意点:WillMount ->组件渲染 -> DidMount
组件构造器
创建每个组件时,都应该有一个构造器,如下
import {Component} from 'react'
class Home extends Component {
constructor(props) {
super(props); // 接收
this.state = {
// .. 该组件定义的数据
};
}
}
父组件中调用该组件
import Home from './views/Home'
class App extends Compoent{
constructor(props){
super(props)
}
this.state = {
userList: {...},
userType: {...}
}
render(){
return(
<Home userList={userList} user> <Home>
)
}
}
在Home组件中即可使用this.props.userList
来获取该数据。
属性拓展
有时候你需要给组件设置多个属性,你不想一个个写下这些属性,或者有时候你甚至不知道这些属性的名称,这时候 spread attributes 的功能就很有用了。
class Dom extends React.Component{
constructor(props){
super(props);
this.state = {
params: {}
}
}
componentWillMount(){
this.state.params.foo = 'foo';
this.state.params.bar = 'bar';
}
render(){
return (
<div ref="container">
<Header ref="header" {...this.state.params}></Header>
</div>
)
}
}
子组件打印props:
JSX
JSX简单地说就是一种把 html 插入 js 中的一种写法,写Vue或者以前传统的MVC框架时,都被 “表现层和逻辑层” 分离这个概念影响颇深。但 React 认为,对于一个组件,表现层和逻辑层本该就是密不可分的。拿上述的Home组件来举例。 JSX通常写在render函数的return ( ) 里面。 return之前是可以写js的。
首先定义父组件传过来的数据,一个是数组,一个是普通对象。
userList: [
{
"userName": "fangMing", "text": "123333", "result": true
},
{
"userName": "zhangSan", "text": "345555", "result": false
}
],
userType:{
"1004":"vip",
"1005":"pinglian",
"1006":"xianxia"
}
class Home extends React.Component{
constructor(props){
super(props);
this.state = {
userList : props.userList,
userType : props.userType
}
}
render(){
// 这里可以写js代码,如定义变量提供给jsx使用
// const role = this.props.role;
return (
<div>
<ul>
{
this.state.userList.map((item,index)=>{
return(
<li key={index}>
{item.userName}
</li>
)
})
}
{
bject.keys(this.state.userType).map(key=>{
return(
<li key={key}>
{key} : {this.state.userType[key]}
</li>
)
})
}
</ul>
</div>
)
}
}
return( ) 中间写HTML模板,需要用到js表达式时用{ },js表达书中需要再嵌入HTML,只需再嵌入 return( )即可。
setState
当我们需要修改模板时,不能直接使用this.state.xxx
, 这将不会使模板重新渲染,必须使用this.setState({})
例: 当我们需要把下列数组每一项的userName改掉
userList: [
{
"userName": "fangMing", "text": "123333", "result": true
},
{
"userName": "zhangSan", "text": "345555", "result": false
}
],
利用Array.map,处理每一项并返回一个新的数组。
componentWillMount(){
setTimeout(()=>{
this.setState({
userList: this.state.userList.map((item,index,arr)=>{
item.userName = '嘻嘻嘻';
return item;
})
})
},2000)
}
⭐Router路由
路由分为HashRouter 和 BrowserRouter。BrowserRouter 较为简介,但线上环境需要服务端配置支持,下面以BrowserRouter为例。
import {
Route,
Switch,
BrowserRouter as Router}
from 'react-router-dom';
import Home from './views/home'
import User from './views/user'
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
render(){
return (
<Router>
<Switch>
<Route path="/user" component={User}></Route>
<Route path="/" component={Home}></Route>
</Switch>
</Router>
);
}
}
注意点:
- Switch 作用是出现重复path时只匹配第一个。
- path=’/’ 要放到最后,任何path都能与 ‘/’ 匹配。
路由跳转
方法1:js控制跳转 , /user中通过this.props.location.query 即可获取参数,但要注意保存数据,刷新后不再获取该数据。
onGo(pathName){
this.props.history.push({
pathname:pathName,
query:{
"uid": '112',
"uname": 'jenson'
}
})
}
// react中类的方法默认是不会绑定 this 的。
//如果你忘记绑定 this.handleClick 并把它传入 onClick
//当你调用这个函数的时候 this 的值会是 undefined。
<button onClick={this.onGo.bind(this,'/user')}>
去User承载页面
</button>
方法2:Link跳转,/user中通过this.props.location.query 即可获取参数,但要注意保存数据,刷新后不再获取该数据。
<Link to={{pathname:"/user",query:{"type":"link跳转"}} }>
<button>gogogo</button>
</Link>
条件渲染
if/else渲染
jsx中是不允许在{}中插入if/else表达式的,因此只能在return之前先判断,这就带来一个弊端,没办法写一些公共部分,如不管是不是vip都要显示的部分,这部分只能在if/else两个return中都写上,代码冗余严重。
render(){
const role = this.state.role;
if(role !== 'vip'){
return (
<h1>不是Vip</h1>
<h1>Home</h1>
)
}
else{
return (
<div>
<h1>欢迎Vip</h1>
<h1>Home</h1>
</div>
)
}
}
Render(IF) 组件
这种方式可以针对单一元素判断渲染,如下,<h1>home<h1>
无论如何都会被渲染。
render(){
function Render ({ if: cond, children }) {
return cond ? children : null
}
const role = this.state.role;
return (
<div>
<Render if={role!=='vip'}>
<h1>不是VIP</h1>
</Render>
<Render if={role =='vip'}>
<h1>欢迎VIP</h1>
</Render>
<h1>Home</h1>
</div>
)
}
立即执行函数(IF)
虽然表达式中不能使用if/else,但可以使用立即执行函数啊!在函数中再使用if/else判断,发现是行得通的。
render(){
return (
<div>
{
(function vip(){
if(role !== 'vip'){
return <h1>不是VIP</h1>
}else{
return <h1>欢迎VIP</h1>
}
})()
}
<h1>Home</h1>
</div>
)
}
React中的条件渲染没有Vue那么方便,但给了开发者很多种拓展的方式实现,可根据不同的业务场景来选择合适的方案。
DOM操作
refs操作
注意,ref要等到render完成后才能取到引用,因此至少在componentDidMount
中使用。
操作组件内的div,ref获得的是真实DOM节点
componentDidMount(){
setTimeout(()=>{
this.refs.title.innerHTML = "DOMDOM"
},1000)
}
render(){
return (
<div>
<h1 ref="title">DOM</h1>
</div>
)
}
操作子组件,ref获得的是一个reactDom,并且可以调用子组件的方法
import Header from '../header'
componentDidMount(){
let header = this.refs.header;
console.log(header)
header.speak() // header中定义的speak方法
}
render(){
return (
<div ref="container">
<Header ref="header"></Header>
</div>
)
}
findDOMNode
ref操作子组件仅仅能获取一个reactDom, 那么如果我们需要操作子组件的Width,Height等属性,就需要获取子组件的真实DOM节点,可用findDOMNode。
import Header from '../header'
componentDidMount(){
let header = this.refs.header;
console.log(header)
header.speak() // header中定义的speak方法
let headerDom = findDOMNode(header) // header的引用ref为参数
console.log(headerDom)
}
render(){
return (
<div ref="container">
<Header ref="header"></Header>
</div>
)
}
异步挂载组件
有时候我们会遇到一个问题,给子组件传递的数据是需要异步请求得来的,如果异步请求还没完成子组件就挂上去了,容易造成报错。
解决方案:
render(){
return(
{this.state.type == 1 && <Header type={this.state.type}></Header>}
)
}
http.get(url).then(res=>{
if(res.code == 0){
this.setState({
type:1
})
}
})
在Route中依然可以使用该方法, 当this.state.matchRoute变为为true时才回去匹配路由。
class Data extends Component {
constructor(props) {
super(props);
this.state = {
matchRoute:false,
};
render(){
return (
<div>
{this.state.matchRoute &&
<Switch>
<Route path="/manage/data/" component ={ props => <Data {...props} />}>
</Route>
</Switch>
}
</div>
);
}
componentDidMount() {
http.get(url).then(res=>{
if(res.code == 0){
this.setState({
matchRoute : true
})
}
})
}
}
子组件传值的注意点
1.在使用路由时,当setState
执行,传给子组件的todos
也更新,此时整个子组件会重新挂载!
componentDidMount(){
this.setState({
todos: this.state.todos.push(...)
})
}
<Router>
<Switch>
<Route path="/" exact component={ () => <TodoList todos={this.state.todos} />}>
</Route>
</Switch>
</Router>
2.不适用路由,单纯使用子组件时,当setState
执行,传给子组件的todos
更新,但子组件不会重新挂载。
componentDidMount(){
this.setState({
todos: this.state.todos.push(...)
})
}
<TodoFooter todos={this.state.todos}></TodoFooter>
Context
用于父组件和子组件及其子孙组件的数据共享,避免数据需要通过Props一层一层传递下去的麻烦。
父组件定义Provider
// ...
// 子组件
import Header from '../header'
import Footer from '../footer'
// 定义Context对象,里面包括Context.Provider和Context.Consumer
export const Context = React.createContext({});
class Home extends React.Component{
constructor(props){
super(props);
this.state = {}
}
render(){
let title = 'title from Home'
let footerText = 'footer text from Home'
return (
<Context.Provider value={{
title:title,
footerText:footerText
}}>
<div ref="container">
<Header ref="header"></Header>
<Footer></Footer>
</div>
</Context.Provider>
)
}
}
export default Home
子组件使用Consumer获取数据
// ..
// 导入父组件导出的Context
import {Context} from '../Home/index.js'
class Header extends React.Component{
constructor(props){
super(props);
this.state = {}
}
render(){
return (
<div>
// 使用Context的Consumer获取数据
<Context.Consumer>
{
(obj) =>
<h1>{obj.title}</h1>
}
</Context.Consumer>
<div>other</div>
</div>
)
}
}
export default Header
如果子组件是一个pure component(hook,如下面的Footer), 可以用useContext获取数据, 父组件定义Context同上
import React,{useState,useEffect,useContext} from 'react'
// 导入Context
import {Context} from '../Home/index.js'
function Footer(props){
// 获取数据
const context = useContext(Context)
return (
<div>
<h1>{context.footerText}</h1>
</div>
);
}
export default Footer;