REACT

react 简介

React 用于构建用户界面的 JavaScript 库,React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

react中文网

react特点

  1. 声明式设计:React采用声明范式,可以轻松描述应用。
  2. 高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。
  3. 灵活:React可以与已知的库或框架很好地配合。

react不同于vue的mvvm,react只负责视图层view层

react 脚手架使用

  • Node >= 8.10 和 npm >= 5.6
  • 安装脚手架 npm install create-react-app -g
  • 查看脚手架版本 create-react-app --version
  • 创建项目 create-react-app xxxx
    • 进入项目目录 cd xxxx
    • 启动项目 npm start
    • 编译打包 npm run build

第一个react应用

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="root"></div>

<!-- 引入react核心库 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- babel用来解析 jsx语法 -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
</script>

JSX

JSX是一种JavaScript的语法扩展,运用于React架构中,其格式比较像是模版语言,但事实上完全是在JavaScript内部实现的。React使用JSX来描述用户界面。使用JSX语法可以让我们在 js编写类似HTML的标签

JSX语法编写的代码并不能直接在浏览器中运行需要经过 babel 进行编译才会被浏览器识别。

1
2
3
// JSX
let template = <h1>Hello, JSX!</h1>
ReactDOM.render(template, document.getElementById('root'));
  • jsx标签必须包含在根标签下,否则会报语法错误。

JSX 注释

在JSX中可以插入注释语句,语法{/* */}

1
2
3
4
5
let template = <div>
{/* jsx 注释 */}
<h1>标题</h1>
<p>段落</p>
</div>

JSX 插入js表达式

使用 花括号 {}可以在JSX插入JS表达式

1
2
3
4
5
let num =199;
let template = <div>
<h1>标题</h1>
<p>段落{num - 19}</p>
</div>

JSX 变量类型

JSX 表达式中可以插入任意类型的变量。

  • JSX插入以下三种类型的值并不能直接渲染到DOM
    • Boolan
    • Null
    • undefined
  • 数组:插入的如果是数组类型那么,会自动展开所有数组成员。

JSX 表达式直接使用插入的数据是安全的

React DOM 在渲染之前默认会 过滤 所有传入的值会将所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 接收用户输入 -->
<input type="text" class="userinput">
<div id="root"></div>
...

document.querySelector('.userinput').onchange = function (ev) {
let value = ev.target.value
let template = <div>
{/* 直接将用户输入的数据插入到JSX表达式 */}
<p>{value}</p>
</div>

ReactDOM.render(template, document.getElementById('root'));
}

JSX 属性

JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex

1
2
3
let template = <div>
<p className='box'>白日依山尽</p>
</div>

使用花括号 {} 为属性插入一个JS表达式

1
2
3
4
5
6
7
let tValue = '这是一个标题'

let template = <div>
// 给属性插入一个js达式
<h2 title={tValue}>标题</h2>
<p className='box'>白日依山尽</p>
</div>

style 属性

在JSX中使用style来设置样式时,style的值必须是一个对象,各个样式属性作为key(驼峰命名),样式值为value

1
2
3
4
var myStyle = {color:"#ff0000",fontSize:40};
let template = <div>
<p style={myStyle}>段落</p>
</div>

JSX 事件

JSX 支持所有的 HTML 元素的事件,但是有一点语法上的不同。

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
1
2
3
4
5
6
7
function  fun1(ev) {
console.log(ev)
}
let template = <div>
{/* 事件名称必须使用小驼峰 */}
<button onClick={fun1}>按钮1</button>
</div>

在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault()

事件回调函数中传递数据

JSX 可以通过箭头函数的方式向事件回调函数中传递数据

1
2
3
4
5
6
function  fun1(...args) {
console.log(args)
}
let template = <div>
<button onClick={ev=> fun1(ev,123,456) }>按钮1</button>
</div>

通过bind函数向传递数据

1
2
3
4
5
6
function  fun1(args) {
console.log(args)
}
let template = <div>
<button onClick={fun1.bind(null,'123') }>按钮1</button>
</div>

更新已渲染的元素

React元素都是不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。

如果要更新已经渲染界面可以通过重新通过render方法渲染。React DOM 会将元素和它的子元素与它们之前的状态进行比较,React 只会更新必要的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let msg='msg'
function changeClick(ev) {
msg = ev.target.value
// 数据改变后重新渲染
let template = <div>
<input type="text" onChange={changeClick} />
<div>{msg}</div>
</div>

ReactDOM.render(template, document.getElementById('root'));
}
let template = <div>
<input type="text" onChange={changeClick} />
<div>{msg}</div>
</div>
ReactDOM.render(template, document.getElementById('root'));

列表渲染

  1. for循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let colors = ['red','blue','green','yellow']
    let colorsJSX = []
    // 将数组中内容展开到JSX表达式中
    for (let i = 0; i < colors.length; i++) {
    colorsJSX.push( <li key={i}>{colors[i]}</li> )
    }

    let template = <div>
    <ul>{colorsJSX}</ul>
    </div>
  2. map()

    1
    2
    3
    4
    5
    6
    7
    let colors = ['red','blue','green','yellow']
    let colorsJSX = colors.map( (item,index) =>
    <li key={index}>{item}</li> )

    let template = <div>
    <ul>{colorsJSX}</ul>
    </div>

    列表中元素需要设置 key 属性,数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。

  3. JSX中嵌入map()。JSX 允许在花括号中嵌入任何表达式

    1
    2
    3
    4
    5
    6
    7
    8
    let colors = ['red','blue','green','yellow']

    let template = <div><ul>
    {
    colors.map( (item,index) =>
    <li key={index}>{item}</li> )
    }
    </ul></div>

组件

组件就是将UI 拆分为独立可复用的代码片段。

注意: 组件名称必须以大写字母开头。

组件创建三种方式:

  1. 函数式组件

    1
    2
    3
    4
    5
    6
    7
    8
    function Home(props) {
    return <div className='home'>
    <h2>home</h2>
    <p>{props.msg}</p>
    </div>
    }

    ReactDOM.render(<Home msg='123'/>, document.getElementById('root'));

    函数式组件在与用户交互时不方便,无状态,适用于静态展示
    简单,性能好,不需要实例化,无生命周期。

  2. React.Component ES6 CLASS语法定义组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Home extends React.Component{
    constructor(props){
    super(props)
    }
    render(){
    return <div>
    <h2>home</h2>
    <p>{this.props.msg}</p>
    </div>
    }
    }

    ReactDOM.render(<Home msg='hello'/>, document.getElementById('root'));

    constructor 构造函数是可选的。

  3. React.createClass ES5语法定义组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const Home = React.createClass({
    render:function(){
    return <div>
    hello,class component,{this.props.msg}
    </div>
    }
    });

    ReactDOM.render(<Home msg='hello'/>, document.getElementById('root'));

    React.createClass 需要在15.5之前版本才能使用,新版本不再支持。

props

通过props可以向组件内部传递数据

props 是只读的,任何时候都不要修改props中的数据

props默认值

  1. React.createClass

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const Home = React.createClass({
    getDefaultProps: function() {
    return {
    msg:'默认值'
    };
    },
    render:function(){
    return <div>
    hello,class component,{this.props.msg}
    </div>
    }
    });
  2. React.Component

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Home extends React.Component{
    render(){
    return <div>
    <h2>home</h2>
    <p>{this.props.msg}</p>
    </div>
    }
    }
    Home.defaultProps ={
    msg:"Hello,World"
    }
  3. 函数式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Home(props) {
    return <div className='home'>
    <h2>home</h2>
    <p>{props.msg}</p>
    </div>
    }
    Home.defaultProps ={
    msg:"Hello,JSX"
    }

props类型验证

自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types 库 代替。

  1. React.createClass

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const Home = React.createClass({
    // 类型验证
    propTypes: {
    msg:React.PropTypes.string,
    },
    render:function(){
    return <div>
    hello,class component,{this.props.msg}
    </div>
    }
    });
  2. 函数式组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script crossorigin src="https://unpkg.com/prop-types@15.7.2/prop-types.js"></script>
    ...
    function Home(props) {
    return <div className='home'>
    <h2>home</h2>
    <p>{props.msg}</p>
    </div>
    }
    //设置组件的属性类型验证
    Home.propTypes ={
    msg:PropTypes.string,
    }

    ReactDOM.render(<Home msg={123}/>, document.getElementById('root'));
  3. React.Component

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script crossorigin src="https://unpkg.com/prop-types@15.7.2/prop-types.js"></script>
    ...
    class Home extends React.Component{
    render(){
    return <div>
    <h2>home</h2>
    <p>{this.props.msg}</p>
    </div>
    }
    }
    Home.propTypes ={
    msg:PropTypes.number,
    }

    ReactDOM.render(<Home msg='998'/>, document.getElementById('root'));

PropTypes以下提供了使用不同验证器的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import PropTypes from 'prop-types'; 

MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,

// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,

// 一个 React 元素。
optionalElement: PropTypes.element,

// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,

// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),

// 你可以让你的 prop 只能是特定的值,指定它为
// 枚举类型。
optionalEnum: PropTypes.oneOf(['News', 'Photos']),

// 一个对象可以是几种类型中的任意一个类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),

// 可以指定一个数组由某一类型的元素组成
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

// 可以指定一个对象由某一类型的值组成
optionalObjectOf: PropTypes.objectOf(PropTypes.number),

// 可以指定一个对象由特定的类型值组成
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),

// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),

// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,

// 任意类型的数据
requiredAny: PropTypes.any.isRequired,

// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},

// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};

组件中事件

1
2
3
4
5
6
7
8
9
10
class Home extends React.Component{
btnClick(ev){
console.log(ev)
}
render(){
return <div>
<button onClick={this.btnClick}>按钮</button>
</div>
}
}

组件中事件回调函数时中使用thisthis的值为undefined

  1. 使用bind函数修正this指向

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Home extends React.Component{
    btnClick(ev){
    console.log(this.props)
    }
    render(){
    return <div>
    {/* 使用bind修正this指向 */}
    <button onClick={this.btnClick.bind(this)}>按钮</button>
    </div>
    }
    }
  2. 也可以在构造函数中修改,修正后可以直接在元素中调用该方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Home extends React.Component{
    constructor(props){
    super()
    // 修正this指向
    this.btnClick = this.btnClick.bind(this)
    }
    btnClick(ev){
    console.log(this)
    }
    render(){
    return <div>
    <button onClick={this.btnClick}>按钮</button>
    </div>
    }
    }
  3. 使用箭头函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Home extends React.Component{
    btnClick(ev){
    console.log(this)
    console.log(ev)
    }
    render(){
    return <div>
    <button onClick={ev=>this.btnClick(ev)}>按钮</button>
    </div>
    }
    }

state

更新已经渲染的元素可以使用 state

  • this.forceUpdate() 强制刷新视图
  1. 在构造函数中初始化 state 数据
  2. 更新 state 中数据调用 setState()

ES6 class语法创建组件 设置 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Home extends React.Component{
constructor(props){
super()
// 初始化
this.state = {
msg:''
}
}
changeClick(ev){
// 修改state中数据
this.setState({msg: ev.target.value })
}
render(){
return <div>
<input type="text" onChange={this.changeClick.bind(this)} />
<h3>{this.state.msg}</h3>
</div>
}
}
  • state 更新数据是异步的如果想要获取更新后的值可以监听setState中的回调函数来获取新值
1
2
3
this.setState(function(newState) {
console.log(newState.msg)
})

createClass组件中的state

createClass组件添加state可以使用 getInitialState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const Home = React.createClass({
//设置组件的state
getInitialState: function() {
return {
num: 0
};
},
render(){

return <div>
<button onClick={this.btnClick}>按钮</button>
<h2>{this.state.num}</h2>
</div>
},
btnClick(ev){
// 修改state中数据
this.setState({
num: this.state.num + 1
})
// 监听state中数据并再次修改
this.setState(state=>{
console.log(state.num)
// 修改state中数据
return {
num: state.num + 1
}
})
}
})

组件生命周期

函数式组件没有状态,没有生命周期。类组件才有生命周期

组件生命周期

生命周期方法

  • componentWillMount() 准备挂载
  • componentDidMount() 挂载完成
  • componentWillReceiveProps() 接收到父组件传递的props数据
  • shouldComponentUpdate(nextProps,nextState) 组件可能要更新
  • componentWillUpdate(nextProps, nextState) 准备更新
  • componentDidUpdate(prevProps,prevState) 更新完成
  • componentWillUnmount() 组件卸载

两个可以触发生命周期的方法

  • forceUpdate() 强制重新渲染界面
  • ReactDOM.unmountComponentAtNode(document.querySelector('#root')) 卸载组件

以上两个方法不推荐使用。

性能优化

shouldComponentUpdate(nextProps,nextState)
此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。你应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()PureComponent 会对 propsstate 进行浅层比较,并减少了跳过必要更新的可能性。

如果你一定要手动编写此函数,可以将 this.propsnextProps 以及 this.statenextState 进行比较,并返回 false 以告知 React 可以跳过更新。请注意,返回 false 并不会阻止子组件在 state 更改时重新渲染。

我们不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。

受控组件

在 HTML 中,表单元素(如<input><textarea><select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态 (mutable state) 通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

一个受控组件中,表单数据是由 React 组件来管理。

  1. <input />输入框

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Home extends React.Component{
    constructor(props){
    super()
    this.state = {
    value1 : ''
    }
    }
    render(){
    return <div>
    <input type="text" value={this.state.value1}
    onChange={ev=>this.setState({value1:ev.target.value})}/>
    <h2>{this.state.value1}</h2>
    </div>
    }
    }
  2. 单选框

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Home extends React.Component{
    constructor(props){
    super()
    this.state = {
    isRemeber : false
    }
    }
    render(){
    return <div>
    <input type="checkbox" checked={this.state.isRemeber}
    onChange={ev=>this.setState({isRemeber: !this.state.isRemeber})}/>
    <h2>{this.state.isRemeber?"true":"false"}</h2>
    </div>
    }
    }
  3. 复选框

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    class Home extends React.Component{
    constructor(props){
    super()
    this.state = {
    colors:['red','blue'],
    }
    this.hanldInput = this.hanldInput.bind(this)
    }
    // 复选框改变事件
    hanldInput(ev){
    //添加
    if(ev.target.checked){
    this.setState({
    colors:[
    ...this.state.colors,
    ev.target.value
    ]
    })
    }else{ //删除
    //获取到要删除的下标
    let i = this.state.colors.indexOf(ev.target.value)
    this.setState({
    colors:[
    ...this.state.colors.slice(0,i),
    ...this.state.colors.slice(i+1)
    ]
    })
    }

    }
    render(){
    return <div>
    <input type='checkbox' checked={this.state.colors.includes('red')}
    onChange={this.hanldInput}
    value='red' />red
    <input type='checkbox' checked={this.state.colors.includes('blue')}
    onChange={this.hanldInput}
    value='blue' />blue
    <input type='checkbox' checked={this.state.colors.includes('green')}
    onChange={this.hanldInput}
    value='green' />green
    <div>颜色:{this.state.colors}</div>
    </div>
    }
    }
  4. select

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Home extends React.Component{
    constructor(props){
    super(props)
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    }
    handleChange(event) {
    this.setState({value: event.target.value});
    console.log(event.target.value)
    }
    render(){
    return <div>
    <h2>home</h2>
    选择你喜欢的风味:
    <select value={this.state.value} onChange={this.handleChange}>
    <option value="grapefruit">葡萄柚</option>
    <option value="lime">酸橙</option>
    <option value="coconut">椰子</option>
    <option value="mango">芒果</option>
    </select>
    </div>
    }
    }

有时使用受控组件会很麻烦,因为需要为数据变化的每种方式都编写事件处理函数,并通过一个 React 组件传递所有的输入 state。当你将之前的代码库转换为 React 或将 React 应用程序与非 React 库集成时,这可能会令人厌烦。在这些情况下,你可能希望使用非受控组件, 这是实现输入表单的另一种方式。

refs

refs属性暴露组件的DOM元素

何时使用 Refs

下面是几个适合使用 refs的情况:

  1. 管理焦点,文本选择或媒体播放。
  2. 触发强制动画。
  3. 集成第三方 DOM 库。

避免使用 refs 来做任何可以通过声明式实现来完成的事情。

refs获取有两种方式

  1. React 16.3 版本之前使用回调函数方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Home extends React.Component{
    constructor(props){
    super()
    // 接收DOM对象
    this.input1 = null
    }
    render(){
    return <div>
    {/*回调函数方式获取ref*/}
    <input type="text" ref={el=>{this.input1 = el;}}/>
    </div>
    }
    componentDidMount() {
    console.log(this.input1)
    }
    }
  2. React 16.3 版本使用的 React.createRef()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Home extends React.Component{
    constructor(props){
    super()
    // 16.3版本使用
    this.input1 = React.createRef()
    }
    render(){
    return <div>
    <input type="text" ref={this.input1} />
    </div>
    }
    componentDidMount() {
    console.log(this.input1)
    }
    }

非受控组件

非受控组件,表单数据将交由 DOM 节点来处理。非受控组件可以 使用 ref 来从 DOM 节点中获取表单数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Home extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}

handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

内容分发

内容分发是指通过组件内容或组件属性向组件传递数据。

默认内容分发

props.children对组件内已有内容进行保留

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Home extends React.Component {
render() {
return <div>
<h2>home</h2>
{/* 使用默认内容分发 */}
{this.props.children}
</div>

}
}

ReactDOM.render(<Home >
{/* 设置分发内容 */}
<h2>内容分发</h2>
</Home>, document.getElementById('root'));

指定内容分发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Home extends React.Component {
render() {
return <div>
<h2>home</h2>
{/* 使用默认内容分发 */}
{this.props.children}
{/* 指定内容分发 */}
{this.props.comp1}
{this.props.comp2}
</div>
}
}
{/* 设置指定分发内容 */}
ReactDOM.render(<Home
comp1={<div>内容1</div>} comp2={<div>内容2</div>}>
{/* 设置默认分发内容 */}
<h2>内容分发</h2>
</Home>, document.getElementById('root'));

高阶组件

高阶组件是参数为组件,返回值为新组件的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//高阶函数
// 参数表示一个组件(首字线要大写)
function myHigerOrder(MyWrapped) {
return class extends React.Component{
constructor(props){
super(props)
this.state={
msg:'hello'
}
}
// 生命周期函数
componentDidMount(){
console.log('componentDidMount...')
}
show(){
console.log('show...')
}
render() {
return <MyWrapped show={this.show} msg={this.state.msg}/>
}
}
}
// 使用定义好的组件利用高阶组件的功能
const Hchild1= myHigerOrder(MyChild1)
const Hchild2= myHigerOrder(MyChild1)

路由

下载路由文件

  • 只下载 react-router-dom,其它不用下载
  • npm install --save react-router-dom
  • cdn <script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script>

ReactRouterDOM类

  • Route : (抽象类) 路由对象,匹配浏览器地址栏url,所有路由记录都必须嵌套在该组件内。
  • BrowserRouter: (Router的实现类)按H5 history 模式匹配url
  • HashRouter:(Router的实现类)按 hash 模式匹配url
  • StaticRouter:(Router的实现类)
  • MemoryRouter:(Router的实现类)
  • Redirect: 重定向,必须配合 <Switch> 一起使用

react不像vue路由规则匹配成功后就不往后面匹配了,react会将所有规则全部匹配。
react 路由规则配合上<Switch>组件使用就会匹配上第一个后就不再向后匹配,如果没有匹配上就继续匹配。

  • Link 声明性,可访问的导航。
  • NavLink:与Link不同点在于会动态的添加一个active类名

NavLink 组件中也要加上 exact 否则匹配时可能会出现都出现 active类名的现象。

  • 也可以指定自已的类名
1
<NavLink to='/goods/food' activeClassName='my-active' exact>食物</NavLink>

路由使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 获取路由对象
const HashRouter = ReactRouterDOM.HashRouter
const Route = ReactRouterDOM.Route //设定规则
const Redirect = ReactRouterDOM.Redirect //重定向
const Switch = ReactRouterDOM.Switch
const Link = ReactRouterDOM.Link
const NavLink = ReactRouterDOM.NavLink

//路由规则匹配匹配成功后要渲染的组件
class Home extends React.Component{
render() {
return(<div>
<h2>Home</h2>
</div>)
}
}
class About extends React.Component{
render() {
return(<div>
<h2>About</h2>
</div>)
}
}

class App extends React.Component{
render(){
return <div>
<HashRouter>
<ul className='navbar'>
<li><Link to='/home'>home</Link></li>
<li><Link to='/about'>about</Link></li>
<li><NavLink to='/goods'>goods</NavLink></li>
</ul>
{/* 定义路由规则 */}
<Switch>
<Route path='/home' component={Home}></Route>
<Route path='/about' component={About}></Route>
<Route path='/goods' render={(props)=>{return(
<div>
<h2>goods</h2>
</div>
)}}></Route>
<Redirect push to='/home' />
</Switch>
</HashRouter>
</div>
}
}

ReactDOM.render(<App />, document.getElementById('app'))

路由重定向

1
<Redirect push to='/'  />
  • pushtrue时,重定向会将新条目推送到历史记录而不是替换当前条目。

路由规则匹配模式

  • exact 精确模式
1
<Route exact path="/one" component={About} />
path location.pathname exact 匹配
/one /one/two true no
/one /one/two false yes
  • strict 严格模式
1
<Route strict path="/one/" component={About} />
path location.pathname 匹配
/one/ /one no
/one/ /one/ yes
/one/ /one/two yes
1
<Route exact strict path="/one" component={About} />
path location.pathname 匹配
/one /one yes
/one /one/ no
/one /one/two no

strict会强制检查路径尾部是否有斜杠。

嵌套路由

  • 嵌套路由,首先会匹配到主路由规则然后再匹配子路由。
  • 主路由匹配规则中不能添加 exact,如果添加的话子路由将匹配不到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Home extends React.Component{
render() {
return(<div>
<h2>Home</h2>
</div>)
}
}

class Goods extends React.Component{
render() {
return(<div>
<h2>Goods</h2>
{/* 配置子路由 */}
<ul className='navbar'>
<li><NavLink to='/goods/cloth' exact>衣服</NavLink></li>
<li><NavLink to='/goods/food' activeClassName='my-active' exact>食物</NavLink></li>
</ul>
<Switch>
<Route path='/goods/cloth' render={()=>{return (<div>
<h2>衣服</h2>
</div>)}}></Route>
<Route path='/goods/food' render={()=>{return (<div>
<h2>食物</h2>
</div>)}}></Route>
<Redirect push to='/goods/cloth' />
</Switch>
</div>)
}
}

class App extends React.Component{
render() {
return(<div>
<h2>嵌套路由</h2>
<HashRouter>
<ul className='navbar'>
<li><NavLink to='/' exact>home</NavLink></li>
<li><NavLink to='/goods'>goods</NavLink></li>

</ul>
<Switch>
<Route path='/' exact component={Home}></Route>
{/* 不能添加 exact 否则主路由匹配不到子路由永远进入不了 */}
<Route path='/goods' component={Goods}></Route>
<Redirect push to='/' />
</Switch>
</HashRouter>
</div>)
}
}

动态路由

1
<Route path="/users/:id" component={User} />
  • props.match.params 接收动态路由传递的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Home extends React.Component{
render() {
return(<div>
<h2>Home</h2>
</div>)
}
}
class Goods extends React.Component{
constructor(props) {
super(props);
this.state={ param:'' }
}
componentDidMount(){
// 获取到路由中传递的数据
let param = this.props.match.params['goods']
console.log(param)
this.setState({param:param})

}
// 数据有更新时渲染数据到页面
componentWillReceiveProps(nextProps){
let param = nextProps.match.params['goods']
console.log(">> " + param)
this.setState({param:param})
}

render() {
return(<div>
<h2>Goods</h2>
<p>{this.state.param}</p>
</div>)
}
}

class App extends React.Component{
render() {
return(<div>
<h2>嵌套路由</h2>

<HashRouter >
<ul className='navbar'>
<li><NavLink to='/' exact>home</NavLink></li>
<li><NavLink to='/food'>food</NavLink></li>
<li><NavLink to='/fruits'>fruits</NavLink></li>
</ul>
<Switch>
<Route path='/' exact component={Home}></Route>
{/* 配置动太路由 */}
<Route path='/:goods' component={Goods}></Route>
<Redirect push to='/' />
</Switch>
</HashRouter>
</div>)
}
}

路由编程式导航

  • 不是路由组件,但想使用路由组件中的对象方法,那么可以使用 withRouter 高阶组件进行封装。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Home extends React.Component{
render() {
return(<div>
<h2>Home</h2>
</div>)
}
}
class Goods extends React.Component{
render() {
return(<div>
<h2>Goods</h2>
</div>)
}
}
// 普通组件需要经过高阶组件withRouter包装才能使用路由中方法
class MyNav extends React.Component{
render() {
return(<div>
<div>
<button onClick={this.goHome.bind(this)}>home</button>
<button onClick={this.goGoods.bind(this)}>food</button>
</div>
</div>)
}
goHome(){
// 必须将该组件通过高阶组件 withRouter 转换后才可以使用push方法
this.props.history.push('/home')
}
goGoods(){
this.props.history.push('/goods')
}
}
// 高阶组件
const withRouter = ReactRouterDOM.withRouter
// 使用普通组件拥有路由组件的方法
const NewNav = withRouter(MyNav)
class App extends React.Component{
render() {
return(<div>
<h2>编辑式导航</h2>
<HashRouter>
<NewNav />
<Switch>
<Route path='/' exact component={Home}></Route>
<Route path='/goods' component={Goods}></Route>
<Redirect push to='/' />
</Switch>
</HashRouter>
</div>)
}
}

路由懒加载

  1. React.lazy() , 加载异步组件

    1
    const Set =  React.lazy(() => import("../views/set"))

    使用 React.lazy 的动态引入特性需要 JS 环境支持 Promise。在 IE11 及以下版本的浏览器中需要通过引入 polyfill 来使用该特性。

  2. React.Suspensesuspence是用来包裹异步组件,添加loading效果等

    1
    2
    3
    4
    5
    <React.Suspense fallback={<div>loading....</div>}>
    ...
    <Route path='/set' component={Set}/>
    ...
    </React.Suspense>

    React.lazy使用import来懒加载组件,importwebpack中最终会调用requireEnsure方法,动态插入script来请求js文件,类似jsonp的形式。

redux

  • 状态模式管理库(实现react中数据共享)
  • 官方的是:flux
  • redux 是第三方的
  • redux英文
  • redux中文
  • cdn https://unpkg.com/redux@4.0.1/dist/redux.js

redux核心概念

  • store 存储数据
  • Reducer 普通函数(不能有异步操作),接收上一次的state以及用户的行为action产生一个新的 state
  • Action 普通对象,记录用户行为以及传输的参数

redux 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//1. 创建 Reducer
function myReducer(state=0,action) {
switch (action.type) {
case 'ADD':
return state+1
break;
case 'SUB':
return state-1
break;
default:
return state
}
}

// 2. 创建 store
// 参数1:创建该容器的Reducer
// 参数2:初始状态值(可选的)
const store = Redux.createStore(myReducer,10)

// 3. 监听 store中数据变化 (可以替代this.forceUpdate()刷新界面)
// 返回值:调用返回值,可以取消监听
const unSubscribe = store.subscribe(()=>{
console.log('a> '+ store.getState())
//数据变化后再次渲染到界面
ReactDOM.render(<App></App>, document.querySelector('#root'))

})
//取消监听
// unSubscribe()

class App extends React.Component{
add(){
store.dispatch({type:'ADD'})
// this.forceUpdate() //强制更新视图
}
sub(){
store.dispatch({type:'SUB'})
// this.forceUpdate()
}
render() {
return(<div>
<h2>Redux</h2>
{/* 将 redux 中数据渲染到界面 */}
<p>{store.getState()}</p>
<button onClick={this.add.bind(this)}>递增</button>
<button onClick={this.sub.bind(this)}>递减</button>
</div>)
}
}
ReactDOM.render(<App></App>, document.querySelector('#root'))

Reducers 合并

  • 调用 Redux.combineReducers() 将多个合并为一个
1
2
3
let rootReducers =  Redux.combineReducers({
taskList, totalCount
})
  • 使用合并过的 Reducers 需要加上合并时的 Reducers 名称。
1
store.getState().taskList

合并后 Reducers 后 相关 typeReducers 都会被触发。

react 与 redux 结合使用

  • react-redux文档
  • npm i react-redux
  • cdn <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.0.3/react-redux.js"></script>
  • react-reduxreactredux 结合在一起

要使用 react-redux 需要先引入 redux

react-redux 核心

  1. Provider 组件,用户于挂载 redux 对象,Provider所包含的所有组件都可以获取到 store

    1
    2
    3
    <Provider store={}>
    ...在这里写组件,必须要有根元素
    </Provider>
  2. 普通组件通过connect 连接可以使用 store中数据

    1
    const NewMyComponent = ReactRedux.connect(mapStateToProps,mapDispatchToProps)(MyComponent)

react-redux 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 创建 Reducer
const count=(state=0,action)=>{
switch (action.type) {
case 'ADD':
return state + 1
case 'SUB':
return state - 1
default:
return state
}
}
// 合并reducer
const rootReducers = Redux.combineReducers({ count })
// 创建 store
const store = Redux.createStore(rootReducers)

// 创建要使用 使用react-redux的组件
class MyComponent extends React.Component{
render(){
return (<div>
<h2>{this.props.title}</h2>
<h6>{ this.props.count }</h6>
<button onClick={this.props.add}>按钮</button>
</div>)
}
}
// 将state 映射成props,
// 参数2 parentProps 接收父级组件传递的 数据,!可选
const mapStateToProps = function (state,parentProps) {
return {
count:state.count,
title:parentProps.title
}
}
// 将dispatch 映射为props
// 参数2 parentProps 接收父级组件传递的 数据,!可选
const mapDispatchToProps = function(dispatch,parentProps) {
return{
add(){
dispatch({
type:'ADD'
})
}
}
}
// 通过高阶组件连接 组件 和 redux
const NewMyComponent = ReactRedux.connect(mapStateToProps,mapDispatchToProps)(MyComponent)

class App extends React.Component{
render() {
return(<ReactRedux.Provider store={store}>
<h2>react-redux</h2>
<NewMyComponent title='标题'/>
</ReactRedux.Provider>)
}
}

ReactDOM.render(<App></App>, document.querySelector('#root'))

hook

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使⽤ state 以及其他的 React 特性。

  • 在我们继续之前,请记住 Hook 是

    • 完全可选的。 你⽆需重写任何已有代码就可以在⼀些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使⽤Hook
    • 100% 向后兼容的。 Hook 不包含任何破坏性改动。
    • 现在可⽤。 Hook 已发布于 v16.8.0。
    • 没有计划从 React 中移除 class。
    • Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:propsstatecontextrefs 以及⽣命周期。Hook 还提供了⼀种更强⼤的⽅式来组合他们。
  • React Hooks解决了什么问题?

    1. 函数组件不能使⽤state,⼀般只⽤于⼀些简单⽆交互的组件,⽤作信息展示,即我们上⾯说的傻⽠组件使⽤,如果需要交互更改状态等复杂逻辑时就需要使⽤class组件了

      React Hooks让我们更好的拥抱函数式编程,让函数式组件也能使⽤state功能,因为函数式组件⽐class组件更简洁好⽤,
      因为React Hooks的出现,相信未来我们会更多的使⽤函数式组件

    2. 副作⽤问题

      • 我们⼀般称数据获取、订阅、定时执⾏任务、⼿动修改ReactDOM这些⾏为都可以称为副作⽤
      • 由于React Hooks的出现,我们可以使⽤useEffect来处理组件副作⽤问题,所以我们的函数式组件也能进⾏副作⽤逻辑的处理了
    3. 有状态的逻辑重⽤组件

    4. 复杂的状态管理

      1. 之前我们使⽤reduxdvamobx第三⽅状态管理器来进⾏复杂的状态管理
      2. 现在我们可以使⽤useReduceruseContext配合使⽤实现复杂状态管理,不⽤再依赖第三⽅
    5. 开发效率和质量问题

      1. 函数式组件⽐class组件简洁,开发的体验更好,效率更⾼同时应⽤的性能也更好

hook使用

HOOK 使用规则

  1. 只在最顶,不要在循环,条件或嵌套函数中调用 Hook。确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。层使用 Hook
  2. 只在 React 函数中调用 Hook,不要在普通的 JavaScript 函数中调用 Hook。

React.useState()

  1. 组件状态管理钩⼦

  2. 能使函数组件能够使⽤state

useState 的使用

1
const [state,setState] = React.useState(initState) 

解构时一定要注意顺序

  • state是要设置的状态
  • setState是更新 state 的⽅法,只是⼀个⽅法名,可以随意更改
  • initState 是初始的 state,可以是随意的数据类型。也可以是回调函数,如果函数时必须返回数据##。
1
2
3
4
5
6
7
8
9
10
11
12
const { useState } = React
function Home() {
const [count, setState] = useState(0)
function btnClick(ev){
setState(count + 1)
}
return (<div>
<h2>home</h2>
<h2>{count}</h2>
<button onClick={btnClick}>btn</button>
</div>)
}

声明多个 state

hook 允许使用多个 state 变量,需要给不同的 state 变量取不同的名称

1
2
3
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);

React.Effect

  1. Effect Hook 可以让你在函数组件中执行副作用操作

  2. 数据获取、订阅、定时执⾏任务、⼿动修改ReactDOM这些⾏为都可以称为副作⽤。
    useEffect就是为了处理这些副作⽤⽽⽣的

  3. useEffect Hook 也可以看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合

  4. useEffect 中可以访问 state 变量(或其他 props)

Effect Hook 语法

1
React.useEffect(callback,array)
  1. callback 回调函数,作⽤是处理副作⽤逻辑。

    1. callback中返回值是可选的。

    2. callback 返回函数的话那么该函数会在`componentWillUnmount ` 之后调用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const { useState, useEffect } = React
      function Home(props) {
      useEffect(()=>{
      //处理这些副作⽤ 的代码
      ...

      // componentWillUnmount` 之后调用
      return ()=>{
      console.log('end')
      }
      })
      return (<div>
      <h2>home</h2>
      </div>)
      }
  1. array(可选参数):数组,⽤于控制useEffect 回调函数的执⾏,有三种情况

    1. 空数组,则只会执⾏⼀次(即初次渲染render)。相当于componentDidMount
    2. ⾮空数组,useEffect会在数组发⽣改变后执⾏。相当于componentDidUpdate
    3. 不填array这个数组,useEffect每次渲染都会执⾏

React.useContext

  1. 更⽅便的实现全局数据共享的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const { useState, useEffect,useContext } = React

    const c1=React.createContext({age:'18',name:'小明'})
    const c2=React.createContext({age:'88',name:'小明1'})

    function Home(props) {
    //使⽤useContext
    const ctx =useContext(c1)
    return (<div>
    <h2>home</h2>
    <h3>{ctx.age}</h3>
    <h3>{ctx.name}</h3>
    </div>)
    }
    function About(props) {
    //使⽤useContext
    const ctx =useContext(c2)
    return (<div>
    <h2>about</h2>
    <h3>{ctx.age}</h3>
    <h3>{ctx.name}</h3>
    </div>)
    }

React.useReducer

  1. useState的⼀个增强体,可以⽤于处理复杂的状态管理

  2. useReducer可以完全替代 useState,只是我们简单的状态管理⽤useState⽐较易⽤,useReducer的设计灵感源⾃于reduxreducer

  3. React.useReducer 语法:

    1
    const [state, dispatch] = useReducer(reducer, initialArg, init);
    • reducer 是⼀个函数,根据action状态处理并更新state
    • initState是初始化的state
    • initAction是useReducer初次执⾏时被处理的action ,(可选的)。
    • 返回:
      • state状态值
      • dispatch是更新state的⽅法,他接受action作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { useReducer } = React
function App(props) {
function reducer(state,action){
console.log(action )
switch (action.type) {
case 'add':
return state+1
default:
return state
}
}
const [state,dispatch] = useReducer(reducer,0)
return <div>
<h2>app</h2>
<h3>{state}</h3>
<button onClick={ev=>{dispatch({type:'add'})}}>btn</button>
</div>
}

自定义HOOK

  1. 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook
  2. 通过自定义 Hook,可以将组件逻辑提取到可重用的函数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { useEffect } = React
//封装的Hooks⽤use开头
// 改变⻚⾯标题的 ⾃定义Hooks**
const useChangeTitle = (title) => {
useEffect(() => {
document.title = title
}, [title])
}
function App(props) {
// 使用自定义HOOK
useChangeTitle("⾃定义修改标题Hooks")

return <div>
<h2>app</h2>
</div>
}