封面 pid: 83647245
基础
React使用JSX来描述组件(相当于Vue的template),在JSX中大括号中的内容将会被当做JS表达式来解析,下面这个例子style属性接收到了一个{color:'#5abebc'}
的对象,{content}被解析为2233
(这类解析会自动转义防止xss攻击)。因为JSX的特性,HTML中跟JS关键字冲突的属性被重新定义如class=>className、for=>htmlFor
。如果需要嵌入解析html代码,使用dangerouslySetInnerHTML
属性
let content = '2233'
let jsx1 = <div style={{color:'#5abebc'}}>{content}</div>
// jsx本身也是表达式,可以作为一个变量,
let jsx2 = (
<p>
<span className="hint">{content}</span>
</p>
)
// 跨行写jsx需要括号包裹
let jsx3 = <div dangerouslySetInnerHTML={{__html:'<p>2333</p>'}}></div>
使用函数或者class来定义一个组件。若使用函数定义,接收一个props
,返回JSX;若使用class定义,需要继承React.Component
、实现render
方法并返回JSX。props
包含了父组件传给子组件的属性,并且传递是单向的
import {Component} from 'react'
function MyCom1(props){
return (
<p>{props.content}</p>
)
}
class MyCom2 extends Component{
render(){
return <p>{this.props.content}</p>
}
}
函数定义的组件是无状态的,通常只能接收props并作展示。class定义的组件是有状态的(state
对象),并且可以基于各种钩子方法和自定方法实现复杂逻辑。在组件中,不能直接操作state
,只能使用this.setState()
来合并更新state
。
class MyCom3 extends Component{
constructor(props){
super(props)
this.state = {content:'2333'} //状态!
}
myfun(){// 假如要在该方法修改state
this.setState({content:'6666'})
}
render(){
return <p>{this.state.content}</p>
}
}
组件的state
和props
可能会异步更新,如果某个状态的要依赖他们确定,setState()
应该接收一个函数来进行更新。setState()
本身也是异步的,如果需要在状态更新后执行某些操作,可以在第二个参数指定回调函数
this.setState(
(state,props)=>{content: state.v1 +props.v2},
()=>console.log('state updated')
)
在JSX中使用JS中的条件判断(if/else、&&、三目)来实现条件渲染(相当于v-if),如果需要阻止渲染,可以返回null
render(){
const sex = this.props.sex
const isLogin = this.props.isLogin
if(!this.props.show)return null
let hn
if(this.props.level===1)
hn = <h1>666</h1>
else
hn = <h2>666</h2>
return(
<div>
{hn}
<p>{sex?'先生':'女士'}</p>
{isLogin && <button>注销</button>}
</div>
)
}
在JSX中使用JS中的map()根据数组遍历生成组件(相当于v-for),遍历生成的组件应该带上key唯一属性
render(){
const data = [1,2,3]
return(
<ul>
{data.map(e=><li key={e}>{e}</li>)}
</ul>
)
}
React中数据流是单向的,表单数据的双向绑定要自己实现,通过props
传入要绑定的数据,通过事件传入回调函数实现数据更新
class NameForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
//回调函数需要进行this绑定
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
/*使用箭头函数实现的this绑定
handleChange = (event)=>{
this.setState({value: event.target.value});
}*/
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
如果多个input
元素使用同一个事件回调函数,可以为其添加name
属性,在回调函数中可以通过event.target.name
来判别是哪个元素。
如果多个子组件需要响应相同的变化数据,可以将他们的state
提升到父组件中,父组件通过props
给子组件分发数据和事件回调。
对于自定义组件,props
上有一个children
属性(即props.children
)用于表示传入的子组件(标签间的内容,相当于slot)
生命周期
React生命周期总体上分为挂载、更新、卸载三个阶段。
挂载阶段(创建组件):
constructor()
,state等数据初始化componentWillMount()
,首次渲染之前执行render()
,(首次)渲染挂载domcomponentDidMount()
,首次渲染之后执行,通常发起请求获取数据
更新阶段(props或state更新):
componentWillReceiveProps(nextProps)
,props更新特有,可以在此根据props的变化执行相关逻辑(更新state、跳转等...)shouldComponentUpdate(nextProps,nextState)
,返回布尔值指示是否终止后续更新阶段,主要用于性能优化(如父组件发生更新但传入的props无变化,可以阻止子组件的更新)componentWillUpdate(nextProps,nextState)
,更新前执行render()
,diff比较新旧vdom,渲染更新domcomponentDidUpdate(nextProps,nextState)
,更新后执行
卸载阶段(销毁组件):
componentWillUnmount()
,销毁前执行,通常清除事件监听等收尾
在16+版本中,引入了getDerivedStateFromProps(nextProps, prevState)
,意在替换componentWillReceiveProps (nextProps)
中更新state的行为。该函数需声明为静态函数,不能访问this
,返回一个对象以更新state,返回null
表示不更新,必须返回两者之一。
还引入了getSnapshotBeforeUpdate(prevProps, prevState)
,这里暂略
进阶(待补充)
Hook
Hook,只用于函数组件的顶层,可以使无状态组件拥有有状态组件的特性。Hook主要有useState
和useEffect
,他们都可以执行多次。useState
用于声明state变量,接收一个值作为变量的初始值,返回变量及更新变量的方法。useEffect
可以让你执行副作用操作(数据请求、dom操作等),第一个参数接收一个函数f1用于执行副作用;f1可以选择返回一个函数f2用于清除副作用,f2会在销毁组件前执行。第二个参数用于指定副作用何时执行,如果不传值副作用会在每次render后执行;如果传入[]
,副作用仅在组件挂载和卸载阶段执行一次;如果传入非空数组,副作用会在数组中的值变化时执行
import React, { useState, useEffect } from 'react'
function Mycom(){
//得到了一个state变量count及修改他的方法
const [count, setCount] = useState(0);
let interval;
useEffect(()=>{
interval=setInterval(()=>console.log(233),1000)
return ()=>clearInterval(interval)
})
//仅在count更新时执行副作用
useEffect(()=>document.title=count,[count])
return(
<span onClick={setCount(count+1)}>{count}</span>
)
}
高阶组件HOC
高阶组件是一个函数,它接收一个组件,返回一个新组件。代码复用是HOC的一个用法,如果多个组件存在相同或相似的逻辑处理,可以将这一部分抽离到高阶组件,让高阶组件去修饰被包裹组件。
HOC应当是纯函数,不应该修改传入的组件,也不应该继承传入的组件,而是采用组合。并且为了不阻碍diff的复用,不要在render函数中使用HOC。
Ref
与Vue相似,React提供ref来直接操作dom或组件,以解决常规方式无法解决的问题。ref使用React.createRef()
创建,并通过ref属性附加到React元素上实现绑定。ref不能绑在函数组件,只能绑在class组件或者HTML元素上。通常在组件初始化时创建ref并在render()
中绑定ref,之后通过ref.current
访问被绑定的dom或组件
class MyCom1 extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount(){
this.myRef.current.focus()
}
render() {
return <input ref={this.myRef} />;
}
}
如果函数组件要使用ref,使用useRef
Hook。
function MyCom2(){
const myRef = useRef(null)
return <input ref={this.myRef} />
}
另一种绑定ref的方法叫回调ref,该方案要求传入ref属性以一个函数,在绑定时执行,函数接收绑定的dom或组件
class MyCom3 extends Component {
constructor(props) {
super(props);
this.myRef = null;
}
setRef = (el) => this.myRef=el
render() {
return <input ref={this.setRef} />;
}
}
路由
yarn add react-dom-router
在React中,路由的配置是像使用组件一样在JSX中进行描述,Router
组件为容器(通常每个组件只有一个且在顶层),里面的Link
组件用于跳转(相当于Vue中的router-link),Route
组件用于描述路由匹配规则、渲染的组件、渲染区域(相当于Vue中带配置项的router-view,但是可以存在多个)。
import { BrowserRouter as Router, Route, Link, hashHistory } from 'react-router-dom'
import Homepage from './Homepage'
import Article from './Article'
render(){
return(
<Router>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/post/1">文章1</Link></li>
</ul>
<Route path="/" exact component={Homepage} />
<Route path="/post/:id" component={Article} />
</Router>
)
}
上面的例子还用到了动态路由,这个用法和Vue是相似的。动态路由的参数在子组件中通过props.match.params
获取。
React中重定向的方法有两种,一种是嵌入Redirect
组件(一旦渲染就跳转),一种是编程式导航
import {Redirect} from 'react-router-dom'
render(){
//编程式导航
//this.props.history.push("/about")
return(
<div>
<Redirect to="/about" />
</div>
)
}
有了上面路由相关组件的对应关系,React中路由嵌套和Vue是类似的(除了Route的表现有点不一样)
全局状态管理
yarn add react-redux redux
Redux中三个重要的概念,Action、Store、Reducer
- Action:请求(通常由type和value组成),组件发起一个请求以修改某个状态
- Store:所有状态的容器,并且联系着Action和Reducer
- Reducer:处理请求的逻辑,根据请求的类型和携带值返回更新后的状态
只有Store有对状态的修改权限,通过Store提供的dispatch(action)
方法发起请求,Store将请求转发给Reducer处理,Reducer处理后返回新的状态让Store进行合并更新
//store/index.js
//创建并导出一个store
import {createStore} from 'redux'
import reducer from './reducer.js'
const store = createStore(reducer)
export default store
//store/reducer.js
const defaultState = {count:1}
export default (state=defaultState, action)=>{
switch(action.type){
'changeCount': return {count:action.value}
default: return state
}
}
//某个组件中修改状态
import store from 'store/index.js'
//...
componentDidMount(){
let state = store.getState()//获取store中的状态
action = {type:'changeCount',value:233}
store.dispatch(action)
}
//...
使用react-redux
简化状态修改流程,使用Provider
组件向子孙组件注入store,在需要跟store互动的子组件中使用connect
方法进行状态和请求的映射。connect
方法是一个高阶函数,第一个参数是store状态到组件props的映射,第二个参数是请求到组件props的映射,执行后返回函数,再接收组件导出即可
//父组件FaCom
import {connect} from 'react-redux'
import store from 'store/index.js'
const FaCom = (props)=>{
return (
<Provider store={store}>
<SonCom />
</Provider>
)
}
export default FaCom
//子组件SonCom
import {connect} from 'react-redux'
const mapState2Props = (state)=>{
return{
//将store中的count映射到组件的props.count
count:state.count
}
}
const mapDispatch2Props = (dispatch)=>{
return{
//将发起请求的方法映射到组件的props.countChange
countChange(e){
dispatch({type:'changeCount',value:e.target.value})
}
}
}
const SonCom = (props) => {
//直接使用映射的属性
return (<input onChange={props.countChange} value={props.count}/>)
}
export default connect(mapState2Props,mapDispatch2Props)(SonCom)
Comments | ?? 条评论