一、写一个时钟
- 用 react 写一个每秒都能够更新一次的时钟
import React from 'react'
import ReactDOM from 'react-dom'
function tick() {let ele = <h1>{ new Date().toLocaleTimeString()}</h1>
// Objects are not valid as a React child (found: Sun Aug 04 2019 20:34:51 GMT+0800 (中国规范工夫)). If you meant to render a collection of children, use an array instead.
// new Date() 是一个对象数据类型的值,React 元素不接管对象作为其子元素
ReactDOM.render(ele, document.querySelector('#root'))
}
tick()
setInterval(tick, 1000) // 如果不包在一个函数中,时钟是不会每秒更新一次
然而 React 和 Vue 雷同都是数据驱动的,然而这个时候和数据驱动没啥关系,每隔 1 秒钟从新创立一个 ele,而后再渲染到页面中,视图才发生变化;为了应用数据驱动,咱们须要应用 React 的组件
二、React 的组件
在 React 组件中,jsx 元素(也称 react 元素)是组件的根本组成单位
在 react 中定义组件有两种形式:
- 函数(function)定义组件
- 类(class)定义组件
- 定义组件的要求:
-
- 组件的名字首字母必须大写,为了在写 jsx 时辨别原生 html 标签
-
- 组件定义后,就能够当做一个标签在 jsx 语法中应用
-
- 如果应用函数定义组件必须返回一个 jsx 元素
2.1 React 的函数组件
react 应用函数定义组件,就是申明一个函数;
- 函数接管一个 props 参数;props 是对象,是在渲染或者父组件通过 prop(属性)传递过去的数据;
- 函数返回一个 jsx 元素,在组件中须要的数据能够通过 props 传入;
// 1. 函数定义组件
function Welcome(props) {
// props 是一个对象,是应用组件时,写在组件行内的属性和属性值组成的;console.log(data)
return (<div>
<p>{props.data.name}; {props.data.age}</p>
<p>{props.x}</p>
</div>)
}
ReactDOM.render(<Welcome data={{name: 'mabin', age: 18}} x='hahah' />, document.querySelector('#root'));
- ReactDOM.render() 会依据第一个参数的类型不同执行不同的操作;
-
- 如果是组件,当 render 执行时,首先会把以后组件的行内属性进行打包封装,把其封装成一个对象,把这个对象传给组件函数
-
- 执行组件函数,获取对应的虚构 DOM 对象
-
- 把虚构 DOM 转成实在 DOM 对象,并且插入到实在的 DOM 中
2.2 React 的 class 组件
通过 class 定义一个组件
- 通过 class 来定义一个组件,须要继承 React 上的 Component 这个类
- 在定义组件上的原型上必须有一个 render 函数,且 render 函数须要返回一个顶级的 jsx 元素
- 看🌰
class Header extends Component {constructor () {super()
}
render () {
// 在 render 函数中通过 this.props 拜访 props
return (<div>
{this.props.content}
</div>)
}
}
class Hello extends Component {constructor (props) {super()
// 留神在构造函数中不能拜访 this.props,props 会作为形参传入
}
render () {
return (
<div>
<Header content="当初是北京工夫:" />
<p>{this.props.data.toLocaleString()}</p>
</div>
)
}
}
// 应用这个组件
ReactDOM.render(<Hello data={new Date()} />, document.getElementById('root'));
- ReactDOM.render() 渲染 class 申明的组件过程:
-
- 找到组件对应的类,而后 new 一下这个类,取得这个类的一个实例
-
- 通过实例找到以后类原型上的 render 函数,让 render 执行接管其返回的虚构 DOM
-
- 将上一步的虚构 DOM 转换成成实在 DOM,插入到页面中
2.3 class 和 function 定义的组件有什么不同
React 也是数据驱动的,当数据发生变化时,视图就会主动发生变化(视图是数据的映射)。组件中的数据有两个起源:props 和 state,其中 props 就是组件被应用时接管的行内属性,是从内部传入的数据,而 state 是组件的公有数据,组件定义时就须要创立;
- class 定义的组件中有 this,state,生命周期的钩子,而 function 申明的组件只有 props;
三、数据映射视图
3.1 属性(props)映射视图
属性(prop)也是组件的数据,而视图是数据的映射,当数据发生变化,组件会主动从新渲染
- 看🌰
function Welcome(props) {return <div>{props.time.toLocaleString()}</div>
}
setInterval(() => {
// 每隔一秒钟 new Date 的值会发生变化,即 Welcome 的 time prop 属性产生了变动,而视图主动变动
let now = new Date()
ReactDOM.render(<Welcome time={now} />, document.querySelector('#root'))
}, 1000)
- 看🌰
把数据通过属性传递给组,参考 前端 react 面试题具体解答
function User(props) {console.log(props)
let {name, age} = props;
return <div>
<p>{name}</p>
<p>{age}</p>
</div>
}
let data = {
name: 'mabin',
age: 18
}
// ReactDOM.render(<User name={data.name} age={data.age} />, document.getElementById('root'))
ReactDOM.render(<User {...data}/>, document.getElementById('root')) // 能够应用开展运算符把一个对象传给组件的 props,等效于下面的写法
3.2 状态(state)映射视图
react 组件的数据有两个起源:props 和 state
属性(props):是父组件传递过去的
状态(state): 是组件本人管控的状态,状态是组件公有的数据
3.2.1 应用 state
- 在 React 中如果应用 state 必须应用 class 创立组件;
- 在 constructor 中初始化一个状态;通过 this.state 赋值一个对象的模式初始化;
- state 中的数据不能够间接批改,如果要更新数据,须要调用 setState 办法,setState 办法会进行合并 setState 有两种写法 一种是对象一种是函数,如果下一个状态依赖上一个状态,咱们须要应用函数的模式
-
- 函数:this.setState((prevState) => {})
-
- 对象:this.setState({num: 5})
- state 产生扭转后触发 render 函数执行更新 DOM
3.2.2 在 react 中绑定事件
- react 绑定事件时,须要应用驼峰命名法的事件名 onClick = {事件处理函数}
- 在定义事件函数时,个别把事件函数申明在原型上,而绑定事件时,通过 this.add 拜访这个事件函数
- 示例:
咱们来写一个计数器感受一下 React 的数据驱动
class Count extends Component {constructor () {super()
// 在 constructor 中初始化一个状态;通过 this.state 赋值一个对象的模式初始化;// 只有用类申明的组件才有 state
this.state = {
num: 1,
x: 2
}
// this.add = this.add.bind(this)
}
add = () => {// 在 react 中如果要批改 状态只能通过 this.setState() 办法批改
// setState 办法会进行合并 setState 有两种写法 一种是对象一种是函数
// 1. setState 能够承受一个回调,回调须要 return 一个新的 state 对象,新的对象中只需蕴含要批改的 属性即可,例如这里咱们要批改 num,return 的对象只须要蕴含 num 不必蕴含 x,react 会主动合并
// 如果下一个状态依赖上一个状态,咱们须要应用函数的模式
/*this.setState((prevState) => {console.log(prevState); // prevState 之前的状态对象 return {num: prevState.num + 1} })*/
// 2. setState 还能够承受一个对象,对象中须要蕴含要更新的 state 属性;this.setState({num: this.state.num + 1})
// 咱们发现,咱们更新数据后,页面中应用 num 的中央的值也主动跟着改了;// react 同样是数据驱动的,当咱们调用 setState 批改 state 时,react 会从新调用 render 函数,失去虚构 DOM 而后调用 DOM-diff 算法,把批改的那一部分从新渲染;}
render () {// react 绑定事件时,须要应用驼峰命名法的事件名 onClick = { 事件处理函数}
// 在定义事件函数时,个别把事件函数申明在原型上,而绑定事件时,通过 this.add 拜访这个事件函数
return (<div>
<p>NUM: {this.state.num} </p>
<p>X: {this.state.x} </p>
<button onClick={this.add}> 给 num 加 1 </button>
</div>)
}
}
ReactDOM.render(<Count />, document.getElementById('root'))
四、属性(props)校验
和 Vue 的 props 一样,React 的 props 同样反对校验;React 的 props 校验须要三方的库 prop-types
4.1 装置 prop-types
yarn add prop-types --save
4.2 应用
应用 类型校验须要 在 class 创立组件时创立动态属性 propTypes,值是一个对象,对象的属性是须要校验的 属性,值对应的是校验规定;
- 类型校验看🌰
static propTypes = {
name: PropType.string.isRequired, // 要求 name 是字符串类型 isRequired 示意必传
age: PropType.number.isRequired // 要求 age 是数字类型,isRequired 示意必传
}
- 此外,还能够给 prop 设置默认值,同样是通过类的动态属性设置,在创立组件时须要配置 defaultProps 动态属性;该属性的值是一个对象,该对象中属性是要设置默认值的 prop,值是 prop 的默认值
static defaultProps = {
name: '珠峰',
age: 10
}
- 残缺🌰
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
import PropType from 'prop-types'
// React 的 props 同样能够校验,然而须要一个第三方的库 prop-types
class User extends Component {constructor (props) {super()
console.log(props) // 对象,把行内属性封装到一个对象中
// props.name = 123 // 如果想对 props 进行批改,能够在 constructor 中进行批改
}
static propTypes = {
name: PropType.string.isRequired, // 要求 name 是字符串类型 isRequired 示意必传
age: PropType.number.isRequired // 要求 age 是数字类型,isRequired 示意必传
}
// 给 props 设置默认值
static defaultProps = {
name: '京东',
age: 19
}
render () {
return (<div>
<p>{this.props.name}</p>
<p>{this.props.age}</p>
</div>)
}
}
let obj = {
name: '张三',
age: 18
};
ReactDOM.render(<User {...obj} />, document.querySelector('#root'));
五、父子组件通信
5.1 父传子
在 React 中,父组件把数据传递给子组件,依然是通过 props 的形式传递;
- 看🌰
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
class Header extends Component {render () {
return (<h1>
<p>{this.props.data}</p>
</h1>)
}
}
// 此时的 Panel 是父组件而 Header 是子组件,父子组件通信时父传子,依然是通过 props 传递的
class Panel extends Component {render () {
return (<div className="container">
<p>{this.props.news}</p>
<Header data={this.props.min} />
</div>)
}
}
let data = {
news: '快下课了',
min: '拖几分钟'
}
ReactDOM.render(<Panel {...data} />, document.getElementById('root'))
5.2 子传父
在 React 中子组件批改父组件的形式和 Vue 不同;子组件如果想批改父组件的数据,父组件在应用子组件的时候,通过 props 传给子组件一个能够批改父组件的办法,当子组件须要批改父组件的数据时,通过 this.props 找到这个办法执行对应的办法
- 看🌰
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
import 'bootstrap/dist/css/bootstrap.css'
class Panel extends Component {
static defaultProps = {a: 1}
constructor () {super()
this.state = {color: 'success'}
}
changeColor = (color) => {
this.setState({color})
}
render () {
return (<div className="container">
<div className={`panel panel-${this.state.color}`}>
<div className="panel-heading">
{this.props.head} </div>
<div className="panel-body">
{this.props.body} </div>
{/* 通过 modifyColor 这个 props 把 Panel 组件的 changeColor 办法传递给 Footer 组件 */} <Footer type={this.state.color}
modifyColor={this.changeColor} />
</div>
</div>)
}
}
class Footer extends Component {change = () => {this.props.modifyColor('danger')
}
render () {
return (<div className="panel-footer">
<button className={`btn btn-${this.props.type}`} onClick={this.change}> 变色 </button>
</div>)
}
}
ReactDOM.render(<Panel head="头信息" body="信息主体"/>, document.getElementById('root'))
// React 同样是单向数据流,即数据只能通过只能从父组件流向子组件
// 所以子组件如果想批改父组件的数据,父组件在应用子组件的时候,通过 props 传给子组件一个能够批改父组件的办法,当子组件须要批改父组件的数据时,通过 this.props 找到这个办法执行对应的办法就能够了