在本文中,融合大量案例和动图进行展示。可以把它当成是react的入门宝库,有不懂的语法知识点时或许在这里可以寻找到你的答案并且通过例子运用起来。
叮,废话不多说,下面来开始探索react的奥秘吧
在react中,最基础的内容便是变量和表达式,具体形式如下:
第一种类型:获取变量、插值
第二种类型:表达式
通常情况下,如果我们要给某一个标签设置类名,那么会给该标签加上一个class。而在react中,如果想要给一个标签加上一个类,那么需要给其加上className。具体代码如下:
同时需要注意的是,在react中,如果要在标签里面写内联样式,那么需要使用双花括号{{}}来表示。
第一种类型:子元素
对于子元素来说,它可以在标签里面进行使用。如下代码所示:
第二种类型:加载组件
如果要在React中加载一个组件,那么我们可以这么处理。具体代码如下:
由此,我们就将一个组件注入到组件当中。
继续,我们来看原生html在react中是如何使用的。先看以下代码:
大家可以看到,如果要在react中使用原生html,那么必须使用constrawHtmlData={__html:rawHtml}这种形式,才能将原生html代码给解析出来。否则的话,react是无法正常将原生html解析出来的。
先看下面这段代码:
.title{font-size:30px;color:red;}.btn-white{color:#333;}.btn-black{background-color:#666;color:#fff;;}此时浏览器的显示效果为:
大家可以看到,当我们theme设置为black时,最终显示的效果就是黑色。如果我们把theme设置为其他状态,那么最终显示的效果就是白色。
先来看一段代码:
大家可以看到,我们也可以通过三元表达式this.state.theme==='black'blackBtn:whiteBtn的方式来对一些条件进行判断。
在react中,使用的是list.map来渲染列表。其中,需要注意的是,list.map()是一个函数,那现在我们假设这个函数里面要遵循一套规则list.map(item=>item.id)。
这个时候,我们视.map为一个数组的重组,那么重组的规则就是item=>item.id。同时,list.map返回的是一个数组。
在这段代码中,我们通过this.clickHandler1=this.clickHandler1.bind(this)来对clickHandler1进行绑定。
还有另外一种关于this的绑定方法。先来看一段代码:
对于上面的这种方式来说,clickHandler2是一个静态方法,此时它的this会指向当前的实例。
大家可以看到,我们通过使用this.clickHandler4.bind(this,item.id,item.title)这种形式来对react中的事件进行参数传递。
如下图所示:
在react中,通过使用onChange事件来手动修改state里面的值。
上面我们已经讲解了input,接下来我们来看textarea和select。
同样地,textarea也是用value和onChange来对值进行绑定。
继续来看select。具体代码如下:
与input和textarea一样,也是通过操作value和onChange,来改变最终的值。
先来看ckeckbox。代码如下:
在上面的代码中,checkbox通过操作checked和onChange,来改变state的值。
radio也是类似,如下代码所示:
对于父子组件的使用来说,我们需要明白三个知识点:props传递数据、props传递函数和props类型检查。
依据以上代码,我们来对props的各个类型进行介绍。
最后一个TodoListDemo是父组件,其他都是子组件。在Input组件和List组件中,我们将props属性的内容,以this.props.xxx的方式,传递给父组件。
React在传递函数这一部分和vue是不一样的。对于vue来说,如果有一个父组件要传递函数给子组件,子组件如果想要触发这个函数,那么需要使用事件传递和$emit的方式来解决。
大家定位到Input组件中,在这里,我们将submitTitle以函数的形式,传递给父组件中的onSubmitTitle。
大家定位到两处props类型检查的地方。使用react中的PropTypes,我们可以对当前所使用的属性进行一个类型检查。比如说:submitTitle:PropTypes.func.isRequired表明的是,submitTitle是一个函数,并且是一个必填项。
就这样,通过上面的例子,我们学习了属性传递、属性验证以及父组件和子组件之间怎么通过传事件的形式来进行通信。
所谓不可变值,即所设置的值永不改变。那这个时候,我们就需要去创建一个副本,来设置state的值。
来看几个要点:
第一点:state要在构造函数中定义。如下代码所示:
第三点,在react中操作数组的值。如下代码所示:
//不可变值(函数式编程,纯函数)-数组constlist5Copy=this.state.list5.slice()list5Copy.splice(2,0,'a')//中间插入/删除this.setState({list1:this.state.list1.concat(100),//追加list2:[...this.state.list2,100],//追加list3:this.state.list3.slice(0,3),//截取list4:this.state.list4.filter(item=>item>100),//筛选list5:list5Copy//其他操作})//注意,不能直接对this.state.list进行pushpopsplice等,这样违反不可变值第四点,在react中操作对象的值。如下代码所示:
//不可变值-对象this.setState({obj1:Object.assign({},this.state.obj1,{a:100}),obj2:{...this.state.obj2,a:100}})//注意,不能直接对this.state.obj进行属性设置,即this.state.obj.xxx这样的形式,这种形式会违反不可变值(2)可能是异步更新react中的state,有可能是异步更新。来看一段代码:
值得注意的是,setTimeout在setState中是同步的。来看一段代码:
还有一个要注意的点是,如果是自己定义的DOM事件,那么在setState中是同步的,用在componentDidMount中。
如果是销毁事件,那么用在componentWillMount生命周期中。代码如下:
setState在传入对象时,更新前会被合并。来看一段代码:
有小伙伴可能会觉得,一下子多个三个setState,那结果应该是+3才是。但其实,如果传入的是对象,那么结果会把三个合并为一个,最终只执行一次。
还有另外一种情况,如果传入的是函数,那么结果不会被合并。来看一段代码:
大家可以看到,如果传入的是函数,那么结果一下子就执行三次了。
react的组件生命周期,有单组件生命周期和父子组件生命周期。其中,父子组件生命周期与Vue类似。
下面附上生命周期的图:
我们先来了解class组件和函数组件分别是什么样的。先看class组件,代码如下:
相反地,class组件就拥有函数组件相异的特点。
在上述表单模块,我们谈论到了受控组件,那接下来,我们就来谈论非受控组件。
所谓非受控组件,就是input里面的值,不受到state的控制。下面我们先来看几种场景。
大家可以看到,如果是非受控组件,那么需要使用defaultValue去控制组件的值。且最终input框里面的内容不论我们怎么改变,都不会影响到state的值。
对于复选框checkbox来说,先看以下代码:
大家可以看到,复选框如果当非受控组件来使用的使用,那么使用defaultCkecked来对值进行控制。同时,我们也看到了,最终不管checked的值如何改变,state的值都不受影响。
在上面的代码中,我们使用通过ref去获取DOM节点,接着去获取到文件的名字。像file这种类型的组件,值并不会一直固定的,所以也是一个非受控组件。
受控组件vs非受控组件的区别如下:
一般情况下,组件默认会按照既定层次嵌套渲染。类似下面这样:
这个时候就需要用到Portals。
.modal{position:fixed;width:300px;height:100px;top:100px;left:50%;margin-left:-150px;background-color:#000;/*opacity:.2;*/color:#fff;text-align:center;}此时,我们来看下浏览器节点的渲染效果。具体如下:
大家可以看到,通过使用ReactDOM.createPortal(),来创建Portals。最终modals节点成功脱离开父组件,并渲染到组件外部。
现在,我们来梳理一些Portals常见的场景。
Portals常用于解决一些css兼容性问题。通常使用场景有:
有时候我们经常会有一些场景出现切换的频率很频繁,比如语言切换、或者是主题切换,那如何把对应的切换信息给有效地传递给每个组件呢?
使用props,又有点繁琐;使用redux,又太小题大做了。
因此,这个需要我们可以用react中的context。
在上图中,我们做到了主题的切换。现在,我们来分析下上述的代码。
首先,我们创建了一个Context,也就是ThemeContext,并传入了light值。
接着,来到了ThemeLink组件。ThemeLink是一个函数式组件,因此,我们可以直接使用ThemeContext.Consumer来对其进行传值。
上面两个组件的值都取到了,但那只是ThemeContext的初始值。取到值了之后呢,我们还要修改值,React会往上找到最近的ThemeContext.Provider,通过value={this.state.theme}这种方式,去修改和使用ThemeContext最终使用的值。
在项目开发时,我们总是会不可避免的去加载一些大组件,这个时候就需要用到异步加载。在vue中,我们通常使用import()来加载异步组件,但在react就不这么使用了。
React通常使用React.lazy和React.Suspense来加载大组件。
如下代码所示:
先来看下面这一段代码:
shouldComponentUpdate(nextProps,nextState){if(nextState.count!==this.state.count ||nextProps.text!==this.props.length){returntrue//可以渲染}returnfalse//不重复渲染}在React中,默认的是,当父组件有更新,子组件也无条件更新。那如果每回都触发更新,肯定不太好。
因此,这个时候我们需要用到shouldComponentUpdate,判断当属性有发生改变时,可以触发渲染。当属性不发生改变时,也就是前后两次的值相同时,就不触发渲染。
那这个时候我们需要思考一个问题:SCU一定要每次都用吗?答案其实不是肯定的。
我们会去用SCU,从某种层面上来讲就是为了优化。因此,我们需要依据当前的开发场景,有需要的时候再去优化。
现在,我们来总结一下SCU的使用方式,具体如下:
PureComponent在react中的使用形式如下:
下面我们来看memo。memo,顾名思义是备忘录的意思。在React中的使用形式如下:
functionMyComponent(props){/*使用props渲染*/}functionareEqual(prevProps,nextProps){/*如果把nextProps传入render方法的返回结果与preProps传入render方法的返回结果一致的话,则返回true,否则返回false*/}exportdefaultReact.memo(MyComponent,areEqual);memo,可以说是函数组件中的PureComponent。同时,使用React.memo()的形式,将我们的函数组件和areEqual的值进行比较,最后返回一个新的函数。
值得注意的是,在React中,浅比较已经适用于大部分情况,一般情况下,尽量不要做深度比较。
在React中,用于做不可变值的有一个库:Immutable.js。这个库有以下几大特点:
下面来看一个使用例子:
constmap1=Immutable.Map({a:1,b:2,c:3})constmap2=map1.set('b',50)map1.get('b')//2map2.get('b')//50基本上现在在开发中都用这个库来处理不可变值的问题。在实际使用中,可以看官方文档按需使用即可。
在React中,对于组件公共逻辑的抽离主要有三种方式要了解。具体如下:
下面将讲解高阶组件HOC和RenderProps。
先看一段代码:
下面来看一个例子,如下代码所示:
值得注意的是,在react中,还有一个比较常见的高阶组件是reduxconnect。用一段代码来演示:
import{connect}from'react-redux';//connect是高阶组件constVisibleTodoList=connect( mapStateToProps,mapDispatchToProps)(TodoList)exportdefaultVisibleTodoList现在,我们来看下connect的源码,具体如下:
值得注意的是,在Vue中有类似于高阶组件的用法,但没有像RenderProps类似的用法,这一点需要稍微留意一下。
下面来看一个例子,具体代码如下:
在上面的代码中,通过this.props.render(this.state)这种形式,将Mouse组件中的属性传递给App,并让App成功使用到Mouse的属性值。
现在,我们来梳理下HOC和RenderProps的区别,具体如下:
对于react来说,它是一个非视图层的轻量级框架,如果要用它来传递数据的话,则要先父传子,然后再慢慢地一层一层往上传递。
但如果用redux的话,假设我们想要某个组件的数据,那这个组件的数据则会通过redux来存放到store中进行管理。之后呢,通过store,再来将数据一步步地往下面的组件进行传递。
值得注意的是,我们可以视Redux为Reducer和Flux的结合。
Redux,实际上就是一个数据层的框架,它把所有的数据都放在了store之中。我们先来看一张图:
大家可以看到中间的store,它里面就存放着所有的数据。继续看store向下的箭头,然后呢,每个组件都要向store里面去拿数据。
我们用一个例子来梳理整张图,具体如下:
React-redux中要了解的几个点是Provider、Connect、mapStateToProps和mapDisptchToProps。
来看以下代码:
再来看一段代码:
import{connect}from'react-redux'import{toggleTodo}from'../actions'importTodoListfrom'../components/TodoList'//不同类型的todo列表constgetVisibleTodos=(todos,filter)=>{switch(filter){case'SHOW_ALL':returntodoscase'SHOW_COMPLETED':returntodos.filter(t=>t.completed)case'SHOW_ACTIVE':returntodos.filter(t=>!t.completed)}}constmapStateToProps=state=>{//state即vuex的总状态,在reducer/index.js中定义return{//根据完成状态,筛选数据todos:getVisibleTodos(state.todos,state.visibilityFilter)}}constmapDispatchToProps=dispatch=>{return{//切换完成状态onTodoClick:id=>{dispatch(toggleTodo(id))}}}//connect高阶组件,将state和dispatch注入到组件props中constVisibleTodoList=connect(mapStateToProps,mapDispatchToProps)(TodoList)exportdefaultVisibleTodoList在上面的代码中,connect将state和dispatch给注入到组件的props中,将属性传递给到TodoList组件。
redux中的同步action如下代码所示:
//同步actionexportconstaddTodo=text=>{//返回action对象return{type:'ADD_TODO',id:nextTodoId++,text}}redux中的异步action如下代码所示:
//异步actionexportconstaddTodoAsync=text=>{//返回函数,其中有dispatch参数return(dispatch)=>{//ajax异步获取数据fetch(url).thne(res=>{//执行异步actiondispatch(addTodo(res.text))})}}(5)Redux数据流图Redux的单项数据流图如下所示:
React-router和vue-router一样,都是两种模式。具体如下:
hash模式的路由配置如下代码所示:
假设现在有父组件RouterComponent,具体代码如下:
继续,我们来看下子组件Project组件时如何进行动态传参的。具体代码如下: