react开发需要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生产版本,create-react-app里已经帮我们把这些东西都安装好了。把通过CRA创建的工程目录下的src目录清空,然后在里面重新创建一个index.js. 写入以下代码:
// 从 react 的包当中引入了 React。只要你要写 React.js 组件就必须引入React, 因为react里有一种语法叫JSX,稍后会讲到JSX,要写JSX,就必须引入React
import React from 'react'
// ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。它是从 react-dom 中引入的,而不是从 react 引入。
import ReactDOM from 'react-dom'
// ReactDOM里有一个render方法,功能就是把组件渲染并且构造 DOM 树,然后插入到页面上某个特定的元素上
ReactDOM.render(
// 这里就比较奇怪了,它并不是一个字符串,看起来像是纯 HTML 代码写在 JavaScript 代码里面。语法错误吗?这并不是合法的 JavaScript 代码, “在 JavaScript 写的标签的”语法叫 JSX- JavaScript XML。
欢迎进入React的世界
,
// 渲染到哪里
document.getElementById('root')
)
如果代码多了之后,不可能像上面一样,一直在render方法里写,所以就需要把里面的代码提出来,定义一个变量,像这样:
import React from 'react'
import ReactDOM from 'react-dom'
// 这里感觉又不习惯了?这是在用JSX定义一下react元素
const app = 欢迎进入React的世界
ReactDOM.render(
app,
document.getElementById('root')
)
(1)函数式组件( 无状态组件 PureComponent)
由于元素没有办法传递参数,所以我们就需要把之前定义的变量改为一个方法,让这个方法去return一个元素:
import React from 'react'
import ReactDOM from 'react-dom'
// 特别注意这里的写法,如果要在JSX里写js表达式(只能是表达式,不能流程控制),就需要加 {},包括注释也是一样,并且可以多层嵌套
const app = (props) => 欢迎进入{props.name}的世界
ReactDOM.render(
app({
name: 'react'
}),
document.getElementById('root')
)
这里我们定义的方法实际上也是react定义组件的第一种方式-定义函数式组件,这也是无状态组件。但是这种写法不符合react的jsx的风格,更好的方式是使用以下方式进行改造
import React from 'react'
import ReactDOM from 'react-dom'
const App = (props) => 欢迎进入{props.name}的世界
ReactDOM.render(
// React组件的调用方式
,
document.getElementById('root')
)
这样一个完整的函数式组件就定义好了。但要注意!注意!注意!组件名必须大写,否则报错。
(2)class组件
ES6的加入让JavaScript直接支持使用class来定义一个类,react的第二种创建组件的方式就是使用的类的继承,ES6 class是目前官方推荐的使用方式,它使用了ES6标准语法来构建,看以下代码:
写法一:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component{
render(){
return(
console.log(this),//此处的this指的就是App组件,自定义在App上的属性就在this的props上
Hello {this.props.name}React!
)
}
}
ReactDOM.render(
,//在App组件上自定义一个name的属性
document.getElementById('root')
)
写法二:【推荐写法】
import React,{Component} from 'react'//此处发生了改变
import ReactDOM from 'react-dom'
class App extends Component{//此处发生了改变
render(){
return(
console.log(this),
Hello {this.props.name}React!
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
两种写法的运行结果和之前完全一样,因为JS里没有真正的class,这个class只是一个语法糖, 但二者的运行机制底层运行机制不一样。
函数式组件是直接调用, 在前面的代码里已经有看到,es6的class组件其实就是一个构造器,每次使用组件都相当于在实例化组件,像这样:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
render () {
return (
欢迎进入{this.props.name}的世界
)
}
}
const app = new App({
name: 'react'
}).render()
ReactDOM.render(
app,
document.getElementById('root')
)
写法三:
将App组件作为单独的文件Hello.js(自己建立的文件)放在src的components(自己建立的文件)的文件夹下面,然后在需要的文件中引入:
Hello.js文件:
import React, { Component } from 'react';
class App extends Component{
render(){
return(
Hello World!
)
}
}
export default App
在需要的文件引入:
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
import App from './components/Hello'
ReactDOM.render(
,
document.getElementById('root')
)
(3)组件的组合、嵌套
将一个组件渲染到某一个节点里的时候,会将这个节点里原有内容覆盖
组件嵌套的方式就是将子组件写入到父组件的模板中去,且React没有Vue中的内容分发机制(slot),所以我们在一个组件的模板中只能看到父子关系
【组合】:示例代码如下,在src下的components文件夹建立Father.js和Son.js文件,
Son.js代码如下:
import React, { Component } from 'react';
class Son extends Component{
render(){
return(
这里是子组件
)
}
}
export default Son
Father.js代码如下:
import React, { Component } from 'react';
class Father extends Component{
render(){
return(
这里是父组件
{this.props.children}//特别注意此处必须要写,相当于给Son组件开辟空间显示,否则Son组件会被父组件的内容覆盖
)
}
}
export default Father
在App.js引入
import React, { Component } from 'react';
import Father from './components/Father'
import Son from './components/Son'
class App extends Component{
render(){
return(
)
}
}
export default App
最后在整个项目的入口文件index.js引入
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
,
document.getElementById('root')
)
【嵌套】:示例代码如下,在src下的components文件夹建立Father.js和Son.js文件,
Son.js代码如下:
import React, { Component } from 'react';
class Son extends Component{
render(){
return(
这是子组件
)
}
}
export default Son
Father.js代码如下:
import React, { Component } from 'react';
import Son from './Son'
class Father extends Component{
render(){
return(
这是父组件
//将子组件嵌套在父组件中,这是与组件嵌套不同之处
)
}
}
export default Father
在App.js引入:
import React, { Component } from 'react';
import Father from './components/Father'
class App extends Component{
render(){
return(
//直接写入父组件
)
}
}
export default App
最后在整个项目的入口文件index.js引入:
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
,
document.getElementById('root')
)
(4)组件中的样式
一共有四种
● 行内样式
import React, { Component } from 'react';
class StyleSheetOne extends Component{
render(){
return(
组件的样式--行内样式
Hello React!
)
}
}
export default StyleSheetOne
● 使用class,通过添加类名实现
单独定义一个css文件,
.style{
color:yellow;
font-size:20px;
}
在在需要的组件文件中引入该css文件,并给需要添加样式的元素添加类名,注意此处添加类名为
className=‘ 类名 ’
import React, { Component } from 'react';
import './StyleSheetTwo.css'
class StyleSheetTwo extends Component{
render(){
return(
组件的样式--class添加类名
Hello React!
)
}
}
export default StyleSheetTwo
● 不同的条件添加不同的样式
有时候需要根据不同的条件添加不同的样式,比如:完成状态,完成是绿色,未完成是红色。那么这种情况下,我们推荐使用classname/classnames这个包
先安装classname第三方包:
cnpm i classname -S
import React, { Component } from 'react'
import classname from 'classname'
class StyleCompThree extends Component{
render () {
return (
组件的样式--classname第三方包
Hello React!
)
}
}
export default StyleCompThree
● css-in-js(在js中写css)
styled-components是针对React写的一套css-in-js框架,简单来讲就是在js中写css。
styled-components是一个第三方包,要安装,安装如下
cnpm i styled-components -S
React认为一切皆组件,那么样式也应该是一个组件
import React, { Component } from 'react';
import styled from 'styled-components'
const Container =styled.div`
width:300px;
height:300px;
background:pink;
`
const Wrapper =styled.section`
width:300px;
height:200px;
background:yellow
h4{
color:red;
}
ul{
background:green;
li{
list-style:none;
color:blue;
font-weight:800
}
}
`
class StyleSheetFour extends Component{
render(){
return(
组件的样式--styled-components
今日头条
- Hello React!
- 你好 React!
)
}
}
export default StyleSheetFour
(5)组件的数据挂载方式
React中数据分为两个部分:属性和状态,这与Vue是不同的,Vue中数据只有状态这一种类型。
【属性】(props):
props是正常从外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props
属性是描述性质、特点的,组件自己不能随意更改。
属性可以定义在组件内部,也可从外界传入,如下代码所示,定义一个Hello.js组件
import React, { Component } from 'react'
class Hello extends Component{
static defaultProps={//定义一个该组件内部的属性
msg:'Hello World!'
}
render(){
console.log(this)//msg为定义在组件内的属性,info为外部传来的属性,但是在this的props属性中都可以找到
return(
内部设置的属性:{this.props.msg}
外部设置的属性:{this.props.info}
)
}
}
export default Hello
在App.js中引入Hello.js组件,并给Hello.js传递一个名为info的属性
import React, { Component } from 'react';
import Hello from './components/Hello'
class App extends Component{
render(){
return(
)
}
}
export default App
★ props.children
我们知道使用组件的时候,可以嵌套。要在自定义组件的使用嵌套结构,就需要使用 props.children 。在实际的工作当中,我们几乎每天都需要用这种方式来编写组件。具体用法见上面的组件嵌套中的使用方法。
★ prop-types
使用prop-types检查props[ 属性验证 ]
React其实是为了构建大型应用程序而生, 在一个大型应用中,根本不知道别人使用你写的组件的时候会传入什么样的参数,有可能会造成应用程序运行不了,但是不报错。为了解决这个问题,React提供了一种机制,让写组件的人可以给组件的props设定参数检查,需要安装和使用prop-types:
$ npm i prop-types -S
import React, { Component } from 'react'
import PropTypes from 'prop-types';//引入
class Hello extends Component{
static defaultProps={//定义一个该组件内部的属性
msg:'Hello World!'
}
render(){
console.log(this)//msg在this的props属性中可以找到
return(
内部设置的属性:{this.props.msg}
外部设置的属性:{this.props.info}
)
}
}
Hello.propTypes={//此处的propTypes是组件下的一个方法
msg:PropTypes.string//此处的PropTypes是引入的prop-types
}
export default Hello
【状态】(state):
状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)
经验: 组件自己的状态只能自己更改
定义state有两种方式
● 方式—:
import React, { Component } from 'react';
class Hello extends Component{
//定义组件的状态
state={
msg:'这是方式一定义组件的状态'
}
render(){
return(
{this.state.msg}
)
}
}
export default Hello
● 方式二:【推荐写法】
import React, { Component } from 'react';
class Hello extends Component{
//定义组件的状态
constructor (props){
super(props)//super继承绑定在当前组件上面的属性
this.state={
info:'这是方式二定义组件的状态'
}
}
render(){
return(
{this.state.info}
)
}
}
export default Hello
this.props和this.state是纯js对象,在vue中,data属性是利用Object.defineProperty处理过的,更改data的数据的时候会触发数据的getter和setter,但是React中没有做这样的处理,如果直接更改的话,react是无法得知的,所以,需要使用特殊的更改状态的方法setState。
★ setstate
例如实现用一个按钮控制内容的显示与隐藏,flag存放在实例的 state 对象当中,组件的 render 函数内,会根据组件的 state 的中的flag确定内容是显示还是隐藏。下面给 button 加上了点击的事件监听。
import React, { Component } from 'react';
class Hello extends Component{
constructor(){
super()
this.state={
flag:true
}
}
change(){
this.setState({
flag:!this.state.flag
})
}
render(){
return(
{this.state.flag && 使用setstate修改数据
}
)
}
}
export default Hello
setState有两个参数,第一个参数可以是对象,也可以是方法 【return一个对象】,我们把这个参数叫做updater
当参数是对象时
this.setState({
flag: !this.state.flag
})
当参数是方法时
this.setState((prevState, props) => {
return {
flag: !prevState.flag
}
})
注意的是这个方法接收两个参数,prevState指的是上一次的state, 第二个是props
将上面的案例可以修改为如下:
import React, { Component } from 'react';
class Hello extends Component{
constructor(){
super()
this.state={
flag:true
}
}
change(){
this.setState((prevState,props)=>{
console.log(prevState)
console.log(props)
return {
flag:!prevState.flag
}
})
}
render(){
return(
{this.state.flag && 使用setstate修改数据
}
)
}
}
export default Hello
setState是异步的,所以想要获取到最新的state,没有办法获取,就有了第二个参数,这是一个可选的回调函数
this.setState((prevState, props) => {
return {
flag: !this.state.flag
}
}, () => {
console.log('回调里的',this.state.flag)
})
console.log('setState外部的',this.state.flag)
【注】:属性vs状态
相似点:都是纯js对象,都会触发render更新,都具有确定性(状态/属性相同,结果相同)
不同点:
属性能从父组件获取,状态不能
属性可以由父组件修改,状态不能
属性能在内部设置默认值,状态也可以
属性不在组件内部修改,状态要改
属性能设置子组件初始值,状态不可以
属性可以修改子组件的值,状态不可以
state 的主要作用是用于组件保存、控制、修改自己的可变状态。state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。state 中状态可以通过 this.setState方法进行更新,setState 会导致组件的重新渲染。
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。
如果搞不清 state 和 props 的使用场景,记住一个简单的规则:尽量少地用 state,多用 props。
没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
react性能优化一个方案: 就是多使用无状态组件( 函数式组件 )
(6)受控组件与非受控组件
React组件的数据渲染是否被调用是通过传递过来的props完全控制,控制则为受控组件,否则非受控组件。
例如:要实现利用父组件的数据控制子组件中内容的显示与隐藏,那么示例代码如下:
父组件代码:
import React, { Component } from 'react';
import Son from './Son'
class Father extends Component{
constructor(){
super()
this.state={
flag:true
}
}
change(){
this.setState({
flag:!this.state.flag
})
}
render(){
return(
Father:
)
}
}
export default Father
子组件代码:
import React, { Component } from 'react';
class Son extends Component{
render(){
return(
{this.props.flag1&&这是被Father组件控制的Son组件
}
)
}
}
export default Son
(7)数据渲染
● 条件渲染
如下示例代码:
import React, { Component } from 'react';
class Event extends Component{
constructor(){
super()
this.state={
flag:false
}
}
changeFlag=()=>{
this.setState({
flag:!this.state.flag
})
}
render(){
return(
条件渲染
{this.state.flag?'Hello World!':'你好 世界!'}
{this.state.flag&&'Hello World!'||'你好 世界!'}
{this.state.flag&& Hello World! ||你好 世界!}
)
}
}
export default Event
● 列表渲染
import React, { Component } from 'react';
//定义一个组件,表示列表中的每一项
const Item=props=>{
console.log(props)
return {props.item1.task}
}
class Event extends Component{
constructor(){
super()
this.state={
list:[
{
id:1,
task:'任务一'
},
{
id:2,
task:'任务二'
}
]
}
}
//定义一个方法遍历list中的数据,此处的item指的是list中的数据项,Item指的是上面定义的组件
//此处的item1={item}是为了在props中获取到list中的数据项,当然item1={item}可以改为{...item}
//对应的上面定义组件的地方{props.item1.task}需要改成{props.task},因为此时的props指的就是
//list中的每一条数据。
renderItem=()=>{
return this.state.list.map(item=>- )
}
render(){
return(
列表循环
{this.renderItem()}//调用方法,渲染
)
}
}
export default Event
(8)高阶组件
高阶组件
高阶组件是一个函数;
这个函数接收一个参数,这个参数是一个组件;
高阶组件封装:
import React, { Component } from 'react';
const HOC = ( Comp ) => {
return class extends Component{
fn () {
//一万行代码
}
render () {
return (
)
}
}
}
export default HOC
使用方法:
例如定义一个A组件
import React, { Component } from 'react';
class CompA extends Component {
render () {
return (
comA组件
)
}
}
export default CompA
在其他组件中引入A组件和高阶组件,并使用高阶组件
import React, { Component } from 'react';
import CompA from './CompA';
import HOC from './HighOrderComp'
const HA = HOC( CompA )
class Comp extends Component {
render () {
return (
高阶组件
)
}
}
export default Comp