React.js新手最佳入门指南😁(持续更新中...)

4,681 阅读14分钟

Hi~我是Tom,这是我整理的React学习路线,文章许多地方对Vue进行的类比,阅读之前我希望你已经十分熟悉Vue.js。

这将是掘金上最全的React指南,里面涉及到React.js,Redux,路由,cli工具,指南主要是以使用步骤为主,告诉你如何直接实现需求,本指南主要是针对于刚入门React的同学,所以一些不必要的步骤或者不必要的说明我都会直接忽略,以防混淆降低学习的效率。

让我们开始吧!

安装

和Vue一样,React先从标签引入形式进行开发学习

  • 引入三个文件

    <!-- babel,用于编译JSX语法 -->
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <!-- react -->
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!-- react-dom -->
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    
  • 创建一个script标签

    //script标签的type要为text/babel,因为这里需要babel来解析JSX语法
    <script type="text/babel">
        function MyFirstComponent(props){
            return(
                <h1>我是tom,这是我创建的第一个react组件</h1>
            )
        }
    
        //调用ReactDOM.render方法,渲染组件
        ReactDOM.render(<MyFirstComponent/>,document.getElementById('root'))
    </script>
    
  • 创建一个用来渲染react组件的html标签

    <div id="root"></div>
    
  • 运行即可,下面贴出根据上面的步骤写的完整HTML代码

    <!DOCTYPE html>
    <html>
    	<head>
    		<meta charset="utf-8">
    		<title>StudyReact</title>
    	</head>
    	<body>
    		<div id="root"></div>
    		
    		<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    		<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    		<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js">			</script>
    		<script type="text/babel">
    		    function MyFirstComponent(props){
    		        return(
    		            <h1>我是tom,这是我创建的第一个react组件</h1>
    		        )
    		    }
    		    
    		    //调用 ReactDOM.render 方法
    		    ReactDOM.render(<MyFirstComponent/>,document.getElementById('root'))
    		</script>
    	</body>
    </html>
    

JSX语法

​ 和vue的单文件.vue有点不同,jsx语法只是将组件的结构(html)和行为(js)整合到一起,没有将表现(css)整合到一起来,css还是采用引入的形式

  • 可以直接写HTML语法:

    • let ele_obj = <div>hello world</div>
      //这里比较离谱的事情是,这里的结构代码不需要加''引号
      //以前我们常用的的做法是将html结构写成字符串的形式,再使用dom对象的innerHTML进行解析。
      
  • JSX插值表达式使用单花括号,区别于vue使用胡子双花括号

    • let name_str = 'ahreal'
      
      let ele_obj = <div>my name is <span>{name_str}</span> </div>
      
  • 使用单标签一定要加 /

    • let ele_obj = <img src="..." />
      
  • 使用class要使用className

    • let ele_obj = <p className="name">ahreal</p>
      
  • 使用行内样式style直接用对象的形式插入

    • let ele_obj = <div style={{'color':'red','fontSize':'20px'}}>hello,world!</div>
          //1.这里插入的是以对象的形式进行插入,所以这里用了双大括号,第一个括号是插值语法,第二个括号是对象的括号
          //2.写样式的时候,如果遇到连接符号font-size这类应该改成驼峰形式fontSize
          //3.中间使用逗号,隔开 ,注意区分html里面是用分号;隔开
          //4.强调外层不要有''引号这些东西,直接两个大括号{{}}
      

组件定义方式

组件名首字母需要大写

通过函数定义

function person(props){
     return <div>hello,{props.name}</div>	{/* 函数定义组件返回的必须是一个jsx对象 */}
}

通过类定义

如果通过类的方式去定义组件,那么组件必须继承于React.Component这个类

class person extends React.Component{
    //必须定义一个render方法,
    render(){
        {/* 也是必须返回一个jsx对象 */}
        return <h1>hello,{this.props.name}</h1> 
    }
    //也可以定义组件的类的方法
    sayName(){
        console.log('我的名字是'+this.props.name)
    }
}

类定义不需要写props这个形参,因为props是继承React.Component来的,只需要this.props即可访问

事件绑定

  • 直接在渲染的jsx对象标签上进行绑定,需要写成驼峰的形式

    比如:onclik 写成 onClick

  • 事件处理的function直接写成类的方法

  • 事件处理funciton内部如果需要访问this(组件本身)的话,直接访问是undefined

    解决方案:

    • 在绑定事件处理函数的时候使用bind明确this指向

      class MyCom extends React.Component{
          render(){
              //这里在调用的时候使用bind
              return <div onClick={this.handleClick.bind(this)}>这是一个自定义组件</div>
          }
          
          handleClick(){
              console.log('我被点击了'+this.props.message)
          }
      }
      
      ReactDOM.render(<MyCom message="ahreal"/>,document.getElementById('root'))
      
    • 使用箭头函数

      class MyCom extends React.Component{
          render(){
              return <div onClick={this.handleClick}>这是一个自定义组件</div>
          }
          //使用箭头函数去定义事件回调
          handleClick=()=>{
              console.log('我被点击了'+this.props.message)
          }
      }
      
  • 事件传参

    • 使用bind传参(不可直接传参,否则会立即调用)

      onClick = {this.handleClick.bind(this,arg1,arg2...)}
      
    • 如果不使用bind,那么就需要再包一层箭头函数

      onClick = { ()=>{this.handleClick(arg1,arg2)} }
      

注意点:

  • 无法通过事件处理函数return false来阻止默认行为,必须使用e.preventDefault()

组件状态state

创建state

如果需要定义组件的自定义属性,在组件的 constructor 构造函数里面去定义state

class Mycom extends React.Component {
    constructor(props){
        super(props)
        //给this.state赋值一个对象,对象的属性就是组件的自定义属性
        this.state = {
            iNum:10
        }
    }
}

修改state

不能直接去修改state的值,否则数据无法驱动关联,需要使用 setState

setState方法接收一个参数,参数为一个对象,类似于小程序原生的setData。

setState方法是一个异步的方法

this.setState({
	iNum:12
})

setState方法也可以接收参数为一个函数,函数的第一个参数为state第二个参数为props

this.setState(function(state,props){
	return {iNum:state.iNum+1}
})

//可用箭头函数简写成
this.setState(state=>({iNum:state.iNum+1}))

//补充一下箭头函数
function(state){
	return {iNum:state.iNum+1}
}
//先去掉function关键字,加上go's to =>
(state)=>{
	return {iNum:state.iNum+1}
}
//因为只有一个参数,接收参数的()可以省略
state=>{
	return {iNum:state.iNum+1}
}
//因为函数内部直接是返回值,所以函数体可以省略return 并且省略{}
state=>{iNum:state.iNum+1}
//因为返回值是一个对象,对象本身的{}会被理解成函数体的{},所以需要再包一层()
state=> ({iNum:state.iNum+1})

当使用函数的形式去传参的时候,可以解决一些由于异步所带来的麻烦的问题

注意:

setState不要依赖this.state或者 this.props等值,因为react会将多个state和props值的变化合并成一起去执行来提高性能,所以如果我们在setState里面具有依赖的话,那么会带来一系列麻烦。

我们使用setState传递一个函数,函数默认有state和props两个形参,利用这两个形参去访问的话,就不会产生问题。

另外,setState还可以接收第二个参数,第二个参数为一个回调函数

经过上面的介绍大家都知道setState设置值是一个异步操作,如果我们需要在设置完state之后做一些逻辑操作,我们可以给setState传递第二个回调形式的参数。

像下面这样。

this.setState({
	name:'ahreal'
},()=>{
	console.log('state值修改成功,现在的name值为'+this.state.name)
})

概念:有状态组件和无状态组件

  • 通过有无state来判断是否是有状态组件
  • 通过function定义的组件都是无状态组件,通过class定义的组件可以是有状态组件,也可以是无状态组件

列表渲染

在React中,列表渲染使用数组的map方法,列表渲染需要使用key

let myArr = ['jack','allen','ahreal']

class MyCom extends React.Component{
    render(){
        return (
        	<ul>
            	{
                    myArr.map((item,index)=>{
                        return <li key={index}>{item}</li>
                    })
                }
            </ul>
        )
    }
}

条件渲染

两种方式做条件渲染

  • 使用 if-else

    • 直接返回jsx对象

      class LoginState extends React.Component{
      	render(){
      		 {
      			if(this.props.isLogin){
      				return <p>已经登录</p>
      			}else{
      				return <p>未登录</p>
      			}
      		}
      	}
      }
      
    • 返回不同的子组件

      function Login(){
      	return <p>已经登录</p>
      }
      
      function Loginout(){
      	return <p>未登录</p>
      }
      
      class LoginState extends React.Component{
      	render(){
      		 {
      			if(this.props.isLogin){
      				return <Login></Login>
      			}else{
      				return <Loginout></Loginout>
      			}
      		}
      	}
      }
      
  • 使用 &&

class Notice extends React.Component{
	
	render(){
		let {username,message} = this.props
		
		{
			return (
				<div>
					<p>欢迎您,{username}</p>
					{message.length&&<p>您有{message.length}条信息</p>}
				</div>
			)
		}
	}
}

如果你想行为和结构区分得更彻底,那么你可以使用变量去保存jsx对象

比如:

function Login(){
    return (
    	<button>登录</button>
    )
}

function LoginOut(){
    return(
    	<button>注销</button>
    )
}

class LoginButton extends React.Component {
    render(){
        let isLogin = false
        let btn
        if(isLogin){
            btn = <LoginOut></LoginOut>
        }else{
            btn = <Login></Login>
        }
        
		return (
        	<div>
            	<h1>登录状态</h1>
                <btn></btn>
            </div>
        )
    }
}

如果你想阻止一整个组件的渲染,那么你return null即可

双向绑定

React中没有帮我们封装类似vue中的V-model

所以我们需要自己去封装双向绑定

React对于绑定了state数据的表单组件称为受控组件

简单的实现步骤:

  1. 初始化默认的数据

    constructor(props){
    	super(props)
    	this.state = {
        //初始化了一个message,message作为双向绑定的数据媒介
    	message:this.props.message
    	}
    }
    
  2. 给输出绑定message

    render(){
    	return (
        <div>
        	<p>{this.state.message}</p>
        
        </div>)
    }
    
  3. 给输入绑定message,并且绑定change事件

    render(){
    	return (
        <div>
        	<p>{this.state.message}</p>
        	<input value={this.state.message} onChange={this.changeMessage.bind(this)}/>
        </div>)
    }
    
  4. 声明changeMessage方法,去动态修改state

    changeMessage(e){
    	let newValue = e.currentTarget.value
    	this.setState(()=>({message:newValue}))
    }
    

Ref的使用

和vue一样,React可以使用ref去标记某个dom对象,然后进行操作

使用步骤:

  1. 在constructor构造函数中创建一个ref对象

    constructor(props){
    	super(props)
    	this.myRef = React.createRef()
    }
    
  2. 在render中使用ref标记dom

    render(){
    	return(
    		<input ref={this.myRef}/>
    	)
    }
    

如此便可讲我们需要的dom使用ref标记了

当我们需要使用的时候可以通过

this.myRef.current

如此去拿到对应的ref所标记的dom

概念:受控组件和非受控组件

  • 受控组件

    组件的数据有和state进行关联的叫受控组件

  • 非受控组件

    组件数据没有和state进行关联的叫非受控组件

生命周期

图示

生命周期官方文档链接

文字说明

constructor

构造函数,在组件初始化的时候会执行

render

这个方法会在componentWillMount方法之后执行,也会在state和props的数据发生变化的时候执行,所以这个方法在组件开始会执行,组件中途也会执行

componentDidMount

方法在组件挂载在页面之后执行

componentDidUpdate

组件更新之后执行

componentWillUnmount

在组件销毁之前执行

shouldComponentUpdate

在组件更新之前执行,这个方法可以决定组件是否更新,renturn true更新 false不更新。

如果返回true 组件会进入componentWillUpdate,然后进入render方法,接着进入componentDidUpdate方法

My-React-Cli

让我们来diy一个简单的react脚手架吧~

核心流程

  1. 初始化项目文件夹

    npm init -y
    
  2. 下载react 和 react-dom

    npm i react react-dom -S
    
  3. 下载webpack 和 webpack-cli

    npm i webpack webpack-cli -D
    
  4. 下载babel的loader 和 babel核心

    npm i babel-loader @babel/core -D
    
  5. 下载babel的react的preset

    npm i @babel/preset-react -D
    
  6. 创建webpack.config.js文件,对loader进行配置

    module:{
    	rules:[
    		{
    			test:/\.js$/,
                //匹配到node_modules文件夹下面的就跳过loader的处理
    			exclude:/node_modules/,
                //使用的loader
                loader:'babel-loader',
    		}
    	]
    }
    
  7. 根目录下创建babel配置文件 .babelrc ,配置react的babel-preset

    {
    	"presets":[
    		"@babel/preset-react"
    	]
    }
    

至此,react简易脚手架已经搭建完成,当然也只是实现对react的js文件进行基本编译,当然一般来说还需配置css-loader,file-loader,dev-server等等,因为这些不在react脚手架的核心里面,所以就不过多赘述。

Router

web开发用的是react-router-dom

react-router-dom和react-router的区别: router-dom是对router的一种补充,新增了类似borswerRouter,Link等组件,安装了react-router-dom就无需再安装react-router

安装

  1. 下包

    npm i react-router-dom -S
    
  2. 导包

    import {HashRouter,Link,Route,Switch,Redirect} from 'react-router-dom'
    

    其中:

    • HashRouter定义哈希路由整体的容器组件
    • Link单个标签,定义路由的链接,通过to属性来定义链接的地址
    • Route单个标签,定义组件的容器标签,通过path定义和Link的to属性对应的地址,component属性定义链接对应的组件
    • Switch多个Route标签外面的容器标签,如果需要定义404跳转和重定向跳转,需要用此标签包裹Route标签
    • Redirect定义路由的重定向,通过from属性定义原始路由,通过to属性定义重定向路由

基本用法

react的路由直接在需要写router-view的页面引入即可

import {HashRouter,Route,Link} from 'react-router-dom'

class MyRouter extends React.Component {
	render(){
		return(
            //HashRouter包裹在最外层作为路由组件的根组件
			<HashRouter>
                //Link组件就是一个a标签,to定义链接的地址
				<Link to="/">router1</Link>
				&nbsp;&nbsp;
				<Link to="/router2">router2</Link>
				&nbsp;&nbsp;
				<Link to="/router3">router3</Link>
				&nbsp;&nbsp;
				<hr/>
                //Route就是一个表明链接对应的组件,同时表示路由组件渲染的位置,exact字段表示精准匹配
				<Route path="/" exact component={router1}></Route>
				<Route path="/router2" component={router2}></Route>
				<Route path="/router3" component={router3}></Route>
			</HashRouter>
		)
	}
}

exact表示这个Route是精准匹配的,react的路由默认是模糊匹配。

404页面

两步走

  1. 最末尾写一个Route标签,不写path属性,表示任意路径都能匹配上

    <Route path="/" exact component={router1}></Route>
    <Route path="/router2" component={router2}></Route>
    <Route path="/router3" component={router3}></Route>
    <Route component={Page404}></Route>
    
  2. 外部包裹一层Switch组件,表示从上到下匹配,只能匹配一个路由

    <Switch>
    	<Route path="/" exact component={router1}></Route>
    	<Route path="/router2" component={router2}></Route>
    	<Route path="/router3" component={router3}></Route>
    	<Route component={Page404}></Route>
    </Switch>
    

Redirect重定向组件

接收一个to和一个from

<Redirect from="/" to="/page1"></Redirect> 

路由嵌套

直接在路由里面写路由子组件即可,当路由里面嵌套路由的时候,最外层不需要再包HashRouter

动态路由传值与取值

和Vue-router的动态路由一样,在定义路由path的时候使用**: 冒号** 。

步骤:

  1. 实现在路由的path声明参数,类似于函数的形参

    import {
      BrowserRouter as Router,
      Switch,
      Route,
      useParams
    } from "react-router-dom";
    
    
    function MyComponent(props){
        return (
        	<Router>
                <Switch>
                    //这里跳转的形参
                	<Route path="/:id" component={<Child/>}></Route>
                </Switch>
            </Router>
        )
    }
    
  2. 跳转的时候在url对应位置写值

    import {
      BrowserRouter as Router,
      Switch,
      Route,
      useParams
    } from "react-router-dom";
    
    
    function MyComponent(props){
        return (
        	<Router>
                //跳转的url直接在对应位置写参数
                <Link to="/ahreal">点我传递ahreal</Link>
            	<Link to="/allen">点我传递allen</Link>
                <Switch>
                    //这里跳转的形参
                	<Route path="/:name" component={<Child/>}></Route>
                </Switch>
            </Router>
        )
    }
    
  3. 对应的组件内通过useParasm获取参数

    import {
      BrowserRouter as Router,
      Switch,
      Route,
      useParams
    } from "react-router-dom";
    
    function Child(props){
        //对应组件里面使用useParams取参数
        let {name} = useParams
        return (
        	<div>传递过来的参数是:{name}</div>
        )
    }
    

编程式导航

首先要知道,组件分为路由组件和非路由组件

  • 路由组件,包裹在Router里并且经过匹配渲染出来的,称为路由组件
  • 非路由组件,不是Router匹配渲染的。

区别

  • 路由组件可以直接从this.props.history上拿到history
  • 非路由组件无法直接拿到history,需要配合withRouter

实现步骤

  • 路由组件:

    //组件方法内部直接获取history
    jump(){
    	this.props.history.push(url)
    }
    
  • 非路由组件:

    //引入withRouter
    import { withRouter } from 'react-router-dom'
    
    //正常定义组件
    class tabBar extends React.Component{
        render(){
            return (
            	<ul>
                	<li onClick={this.handleClick.bind(this,'/home')}>首页</li>
                    <li onClick={this.handleClick.bind(this,'/cate')}>分类</li>
                    <li onClick={this.handleClick.bind(this,'/home')}>个人中心</li>
                </ul>
            )
        }
        
        handleClick(url){
            //如路由组件一样直接使用this.props.history
            this.props.history.push(url)
        }
    }
    
    //暴露的时候需要使用装饰者设计模式使用withRouter包裹一下组件,注意,一定要是最外层
    export default withRouter(tabBar)
    

自定义路由(路由守卫)

我们上面说到Route组件可以接收一个Component组件,当path匹配上的时候,这个Route组件就会被渲染出来。我们还可以在路径匹配之后做一点事情,这一点类似于Vue中的路由守卫。

用到的还是Route这个组件,只不过这次组件不通过Component去传递数据,通过 render 属性。

import {Route} from 'react-router-dom'

function Custom(){
    return (
    	<Route path="/index" Render={()=>{
                //isLogin判断用户是否登录,如果登录了渲染首页,没有登录渲染登录
               	if(isLogin){
                   return <Index></Index>
                }else{
                   return <Login></Login>
                }
            }}/>
    )
}

还有一种常见的情况,使用 children 属性去帮我们做匹配渲染

匹配渲染

假设我们这里有个需求,我们要做一个按钮,处于index页面的时候,首页按钮高亮

import {Route} from 'react-router-dom'

function Custom(){
    return (
        //match为一个布尔值,由Route默认传入,告知匹配结果。
    	<Route path="/index" children={({match})=>{
                return(
                    //当匹配成功的时候,添加类名active高亮,否则移除active
                    <Button className={ match ?'active':'' }>首页</Button>
                )
            }}></Route>
    )
}

传值

父子之间

  • 父传子

    父组件在引用子组件的时候通过行内属性的形式直接传递,子组件通过this.props.属性名去获取传递过来的值

    注意,这里和Vue一样,具有单向数据流的概念,不可以直接修改由父组件传递过来的props属性

  • 子传父

    父组件在自身声明一个接收数据的方法,将这个方法通过行内属性的形式传递给子组件,子组件接收这个方法调用,通过方法传参的形式传值

兄弟之间(bus模式)

  • 使用react内部模块events中的EventEmitter类实例化一个bus对象,通过发布订阅的模式传值,组件中通过bus.emit来触发自定义事件,接收数据的组件通过bus.on来监听事件。

Redux

react中统一的状态管理

基本流程

**React Component:**react组件,是redux的使用者,获取redux值或者修改redux的值

**Store:**Store对象,数据仓库,组件需要获取或者修改数据需要通过store对象。

**Reducers:**负责数据的操作,定义Action的type对应的操作,定义数据仓库默认的初始值。

**Action Creators:**数据操作action,类似一个工单,组件如果需要修改redux的数据,需要创建一个action,action声明type和value,交给store去操作数据

安装部署redux

  1. 下包

    npm install redux -S
    
  2. 创建store文件夹

  3. 创建index.js

    import {createStore} from 'redux'
    
    import reducer from './reducer.js'
    
    let store = createStore(reducer)
    
    export default store
    
  4. 创建reducer.js

    let defaultData = {
    	
    }
    
    function reducer(state=defaultData,action){
    	
    	return state
    }
    
    export default reducer
    

使用流程

**取值:**组件引入store对象,调用store对象的getState方法获取当前的数据仓库。

let storeData = store.getData()

**改值:**事先在reducer文件声明相应action type对应的处理方式,组件创建一个Action工单对象,调用store对象dispatch方法处理,如果我们需要监听值的变化的话,我们需要调用一下subscribe并且传递一个回调

  1. 在reducer文件里面声明处理方式

    let reducer = (state=defaultData,action)=>{
        if(action.type==='addNum'){
            //深拷贝一个state,在拷贝的state上面对值进行操作
            let newState = JSON.parse(JSON.stringify(state))
            newState.Num += action.value
          	//最后要返回这个新的state
            return newState
        }
        return state
    }
    
    export default reducer
    
  2. 在component组件里面制作一个工单,并且指派给store去处理

    import store from '...'
    
    addNum(){
        //创建一个action工单
    	let action = {
            type:'addNum',
            value:10
        }
        //调用store的dispatch方法提交工单
       	store.dispatch(action)
    }
    
  3. 一般在constructor调用store的subscribe方法,监听store的变化

    import store from '...'
    
    constructor(props){
        super(props)
        //订阅redux的数据变化,当数据变化的时候会执行传递的回调
        store.subscribe(this.handleStoreChange.bind(this))
    }
    
    handleStoreChange(){
        //一旦被执行了,就意味着redux数据变化了,需要重新获取一下
        let storeData = store.getState()
        ...
    }
    

订阅以及退订

**订阅:store.subscribe(fn):**订阅store数据变化,当store的数据发生改变的时候,自动执行传入的回调。

**退订:**当调用store.subscribe去订阅store数据变化的时候,会返回一个退订的方法,我们只需要保存这个方法,在需要退订的时候调用一下即可。

**退订操作一定要在组件销毁前执行一下,**因为React中路由组件的切换是组件不断的构建-销毁的过程,这时候如果没有退订的话,每次切换来回一次组件,就会创建一次组件,执行一次constructor方法,就会订阅一次(我们一般把store.subscribe方法的调用放置在constructor中),**一直重复订阅的话会造成内存泄漏,**最后导致内存溢出

正常订阅以及退订的操作

import store from '...'

class Mycomponent extends React.Component {
    constructor(porps){
        super(props)
        //调用store.subscribe订阅数据变化,同时将返回的退订方法保存在this上
        this.unSubscribe = store.subscribe(handleChange)
    }
    ...
    componentWillUnmount(){
        //调用一下退订的方法
        this.unSubscribe()
    }
}

React-Redux

首先你要明白的事情是,redux并不是专门为react服务的,他的运作完全不需要react,redux和其他第三方框架配合一样可以使用。

react-redux是对redux的一种扩充,能提高redux在react的性能,并且能够使代码更加的优雅。

react-redux依赖于react以及redux,所以使用react-redux之前,是基于你react以及redux已经安装完成。

安装以及使用

首先你应该先完成redux的安装并且部署好store以及reducer,参考笔记上面redux的安装部署。

  1. 下载 react-redux

    npm install react-redux -S
    
  2. 在main.js使用react的context特性注入store

    import store from './store/store.js'
    
    //导入react-redux的Provider
    import {Provider} from 'react-redux'
    
    //注入store
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
     document.getElementById('root')
    )
    
  3. 在组件里面导入connect

    import { connect } from 'react-redux'
    
  4. 声明两个注入props的属性和方法

    //这里的参数state是个默认参数,这里的state指的是store的state,而不是组件自身的state
    let mapStateToProps = state=>{
    	return{
            //写属性
    	}
    }
    
    let mapDispatchToProps = dispatch=>{
    	return{
    		//写方法
    	}
    }
    
  5. 使用connect修饰组件并导出组件

    //connect接收两个参数,是上面定义的两个方法,connect返回一个方法,方法接收组件本身作为参数
    export default connect(mapStateToProps,mapDispatchToProps)(MyComponent)
    

与只使用redux的区别以及优点

  • 无需在组件文件里面导入store
  • 无需调用subscribe来监听数据变化,自动监听
  • 如果组件是无状态组件(没有自己的state),那么组件可以直接改成使用function声明(之前只使用redux,由于redux的state需要在组件的constructor里面去获取,所以只能使用class的形式去定义组件),改用function声明组件,能提高性能
  • 理由同上一点,使得代码更加简洁优雅

react及redux的chrome开发者工具

使用起来和vue dev-tools一毛一样,不过多赘述。

下载链接:pan.baidu.com/s/1Iq-J3u-2… 提取码:f7dg

安装流程:

  1. 网盘里面一共有两个文件夹,分别是react开发者工具和redux开发者工具,下载。
  2. 下载完成之后,打开chrom菜单,更多工具,扩展程序。
  3. 右上角又一个开发者模式,请打开。
  4. 打开开发者模式以后,选择左上角加载已解压的扩展程序,选择刚才下载的两个文件夹就OK。

注意,redux的开发者工具不是开箱即用,需要在代码里面,创建store的时候加上一句

//原本只传递一个参数reducer,现在多传递一个参数,复制过去即可。
let store = createStore(reducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())

插槽

官方不叫他插槽,官方叫他组合

我认为他和效果和Vue中插槽的概念非常接近,而且插槽的话对于这一块的知识点的描述更加形象。

用法

React的props可以传递一个JSX对象,通过{this.props.XX}去使用外层传递进来的JSX对象

//定义一个我们即将传递进来的子组件
function hello(props){
    return <h1>Hello world</h1>
}

//定义一个props接受子组件的组件
class MyComponent extends React.Component{
    render(){
        return(
        <div>
      		<h1>你好世界</h1>
            {this.props.children}
        </div>
      )
    }
}

//使用props将子组件传递进去
ReactDOM.render(<MyComponent children={<hello/>} />, document.getElementById('root'));

概念:React中的副作用操作

数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。

一般说到函数的副作用指的是,函数体内部做了一些和参数返回值无关的操作,比如说访问全局变量,修改全局变量等等一些和本函数无关的操作称为函数的副作用。

Hook

Hook是React16.8新推出的一个特性,可以在使用function定义组件的时候,使用state等其他react的特性。

Hook是一种渐进策略,也就是说即使你的项目之前没有使用hook,你完全可以选择使用或者不使用hook,

Hook不可以在class组件中使用。

useState

这就是一个hook,能够帮助你在function组件定义State。

像这样:

这是一个计步器,每次点击button的时候count这个state属性++

//导入React使用默认导入,useState使用的是具名导入,这个和react封装的暴露策略有关。
import React,{ useState } from 'react'

function Example() {
  // useState本身是一个方法,方法接收一个参数,方法返回一个数组,使用解构赋值的形式接收并且声明。
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

我们来具体讲一下 useState

useState本身是一个方法,方法接收一个参数,这个参数作为我们要定义的 state的初始值

比如说我们:

//这里是es6的解构赋值特性
const [count, setCount] = useState(0);

等价于你在class组件中:

constructor(props){
	super(props)
	this.state:{
        count:0
    }
}

那么这个setCount是什么呢?setCount是用来修改Count这个State的方法。

比如说我们这样:

setCount(20)

等价于你在class组件中:

this.setState({
	count:20
})

我们使用hook的setCount和React自带的setState区别在于,setState是合并state,和setCount是替换值,毕竟setCount只为一个state服务。

这个设置state初始值,和调用方法修改state并不难。

当然如果你有多个state,那么你只需调用多次useState即可。

useEffect

useEffect这个Hook使你的function组件具有生命周期的能力!能够使用componentDidMount,componentDidUpdate,componentWillUnmount这三个生命周期函数。

最基本的使用useEffect

我们在上一个例子的计步器的基础上,将Count绑定到浏览器title。

import React,{ useState,useEffect } from 'react'

function Example() {
  //这里最好使用const来做声明关键字,防止我们意外直接修改state而没有通过set方法去设置。
  const [count, setCount] = useState(0);
  
  //useEffect方法接收一个参数,参数为一个函数,这个函数会在Dom渲染的时候调用,包括第一次渲染。
  //这里useEffect接收的函数在react中称为副作用函数。
  useEffect(()=>{
      document.title = `你点击了${count}次!`
  })

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

下面我们再进行与Class组件类比:

比如你这样去写useEffect这个hook

useEffect(()=>{
      document.title = `你点击了${count}次!`
})

在Class组件中等价于:

//组件挂载到页面上后的生命周期钩子
componentDidMount(){
	document.title = `你点击了${this.state.count}次!`
}
//组件State更新之后的生命周期钩子
componentDidUpDate(){
	document.title = `你点击了${this.state.count}次!`
}

这里useEffect将两个生命生命周期合二为一了,简化了代码,但同时也限制了逻辑代码的灵活性。

对于此,React官方文档给到的解释是:

注意,在这个 class 中,我们需要在两个生命周期函数中编写重复的代码。

这是因为很多情况下,我们希望在组件加载和更新时执行同样的操作。从概念上说,我们希望它在每次渲染之后执行 —— 但 React 的 class 组件没有提供这样的方法。即使我们提取出一个方法,我们还是要在两个地方调用它。

使用useEffect实现componentWillUnmount

上面只介绍了如何实现componentDidMount,componentDidUpdate,那如何实现componentWillUnmount这个生命周期呢?

副作用函数中返回一个函数即可,返回的这个函数就是componentWillUnmount。

useEffect(()=>{
      document.title = `你点击了${count}次!`
      
      let UnmountFn = ()=>{
          alert('组件被卸载了!')
      }
      return UnmountFn
})

返回的这个函数只有在组件卸载的时候才会被执行。

性能优化

在class组件中,componentDidUpdate在每次数据发生改变的时候都会被执行,我们常用一个判断需要的值的改变来节约性能,就像这样:

我们需要在name值发生改变的时候重新赋值给fullname

componentDidUpdate(prevProps,prevState){
	//当发生改变的值是state的name的时候,才继续往下执行
	if(prevState.name != this.state.name){
		this.setState((state)=>{
			return {
				fullname:'黄'+ state.name
			}
		})
	}
}

在effect中,我们可以通过第二次参数的形式决定这个hook执行与否

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

更加准确的理解

我们上面类比class组件的生命周期来解释useEffect的作用,方便理解,但是我个人认为还不够准确。

  • class组件的componentWillUnmount是在组件卸载的时候执行,而上文将其类比的useEffect返回值函数是在state每次发生改变的时候都会执行,只不过是在Hook挂载的时候不会执行。
  • 观察的点不同,组件挂载我认为和Hook挂载不同,即使componentDidMount和useEffect在看起来貌似是同一时刻执行,componentDidMount观察的是组件的DOM挂载,而useEffect观察的是State创建或者更新(从另外一个维度理解,state创建和更新本质上都是赋值。)

Hook的使用规则

从官方文档搬来的,我觉得总结的很好。

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)

自定义Hook

我们可以将一些相同的逻辑抽离出来,注入组件中。

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。

一个自定义的Hook例如:

import React, { useState, useEffect } from 'react';
//这是一个可以通过friendID去判断是否在线的自定义Hook
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });
  //这里只是返回了isOnline这个属性,当然你连setIsOnline也可以返回出去,可以让组件调用的时候接收这个方法,在必要的时候自行修改Hook的State
  return isOnline;
}

自定义Hook看起来特别的像一个函数的使用,传递一个参数,得到一个结果。

定义Hook和函数不同的是,Hook可以利用起生命周期钩子,以及有Hook内部自己的数据。

注意:自定义Hook每次调用的时候会创建一个新的State作用域(如果你有使用useState的话),所以不用担心多次调用Hook导致State互相影响

自定义Hook服用逻辑,但不服用State,作用域还是独立的

如何在组件中使用这个自定义Hook呢?

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

用一个变量保存即可,无需Hook关心内部的逻辑,内部帮你绑定了生命周期,帮你创建State作用域,这些你都不用关心,直接用即可。

代码分割(待补充)

React的懒加载

  1. 动态 import( )
  2. React.lazy( )
  3. 基于路由

Context

Context,翻译过来就是上下文的意思,我们有时候会出现这么一个情况,某一些特定的状态,我只想在一个组件树上共享,我既不想一层一层向下传值,又不想他是一个全局变量(毕竟其他组件树不需要这些状态),那么Context就是为了解决这个情况设计。

挂载(生产)

我们把需要传递下去的值挂载在组件树的最顶端,挂载完以后就可以在挂载点的子孙组件访问

使用(消费)

分为两种情况:函数组件和类组件

函数组件:

函数组件需通过Context.Consumer获取。

类组件:

类组件重写自身类的contextType,将其指向需要获取值得Context即可。

使用流程

  • 我们先来创建一个Context对象,这里 MyContext 就是一个Context对象

    const MyContext = React.createContext('ahreal')
    
  • 接着我们把Provider挂在我们需要传递特性的组件树最顶端。(挂载)

    <MyContext.Provider>
        <Father>
    		<Son></Son>
        </Father>
    </MyContext.Provider>
    
  • 我们需要取值的时候分两种。(取值)

    1. 类组件(假设son组件是一个类组件)

      class Son extends React.Component{
      	render(){
              let value = this.context
          }
      }
      //需要重写类的contextType,当然这个MyContext如果有需要的话要从定义的地方导入
      Son.contextType = MyContext
      
    2. 函数组件

      function Son(props){
          return(
          <MyContext.Consumer>
      		{
               	value = > (
                  	<div>
               			...
                      </div>
                  )       
               }
      	</MyContext.Consumer>
          )
      }
      

Cli(待补充)

...