React 组件基本使用(一)
React 提供了两种方式来声明组件,一种是函数式,一种是类式,就是用es6 class, 我们所有的组件都继承自React.Component.
函数式很简单,就像我们平常写函数一个,接受一个参数作为输入,然后进行相应的输出,只不过它输出的jsx.
// welcome 函数式组件。 function Welcome(props) { return <h1> {props.name}</h1> }
类式 写法如下:
class Welcome extends React.Component { render() { return <h1> {this.props.name}</h1> } }
声明组件以后,怎么使用这个组件? 如果组件还要接受参数,怎样进行参数传递? 把组件想成一个html标签就可以了,html标签怎么使用,组件就怎么使用。html标签有两种使用方式,一种是 img 自闭合标签<img />,一种是 h1之类的双标签<h1></h1>。同理,组件也有这两种使用方式。传参则像是给html标签写属性,属性名 = 属性值,如name =”Jason” , 组件内部的props 则把这些属性组合在一起形成对象{name: “jason”}
<Welcome name="Jason" /> // 标签一定要闭合,就是后面的/不能忘记
<Welcome></Welcome>
使用组件,它返回了 <h1> Jason</h1>, 很像html 代码,其实它是 React 所说的虚拟DOM, 并不是真实的DOM, 我们要把虚拟DOM 渲染成真实的DOM,才能显示到页面中,这需要用到ReactDOM的render 方法,它接受的第一个参数,就是虚拟DOM, 第二个参数就是我们要把DOM 渲染到什么地方。
ReactDOM.render( <Welcome name="Jason" />, document.getElementById('root') );
这就是react组件最简单的使用,现在写一个html 页面,引入react reactdom 等,可以看到页面中输入了Jason
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World</title> <script src="https://unpkg.com/react@latest/dist/react.js"></script> <script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script> <script src="https://unpkg.com/[email protected]/babel.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> // welcome 函数式组件。 function Welcome(props) { return <h1> {props.name}</h1> } // 渲染成真实的DOM ReactDOM.render( <Welcome name="Jason" />, document.getElementById('root') ); </script> </body> </html>
属性值是一个字符串,是最简单的方式进行传值,其实属性值可以是任何的js 表达式,只要把它们包括在{}中,在jsx 中,{}里面的所有东西都当作js表达式进行解析。比如我们向组件中传递一个数字1,我们就可以写 num = {1}, 更改script 标签的内容进行测试
function Welcome(props) { // 新增num 属性 return <h1> {props.name} {props.num}</h1> } ReactDOM.render( <Welcome name="Jason" num = {1}/>, //js表达式进行传值 document.getElementById('root') );
如果我们要传递很多属性,这么 一个一个列出来,非常麻烦,这时可以使用对象,但如果用对象进行传值,又不符合 属性名=属性值的写法,这时要用到es6中的扩展运算符..., React 对es6 中的扩展运算符(…)进行扩展,它能运用到对象上,对对象进行分割。{…obj}; var obj = {name:”sam”, age: “28”} {…obj} => name=”sam” , age=”28”, 正好对应父组件向子组件传递数据。对上面的代码进行更改
var obj = { name: "jason", num : 28 } function Welcome(props) { return <h1> {props.name} {props.num}</h1> } ReactDOM.render( <Welcome {...obj}/>, // 对象传递数据 document.getElementById('root') );
为了渲染时更加简单,我们可以把组件赋值给一个变量,然后在 render 方法中直接使用这个变量作为第一个参数, 这里对name 参数进行了更改,以便看出区别。可以看到页面中输出sam
// 组件的调用结果赋值给一个变量 const nameElem = <Welcome name="sam" /> ReactDOM.render( nameElem, // 在render 中直接使用变量 document.getElementById('root') );
有时我们还要把小的组件进行组合,形成大的组件,这里也可以用函数声明的方式
// 组件的组合 function App() { return ( <div> <Welcome name="Sam" /> <Welcome name="Jason" /> </div> ); }
对于这样的组件,在调用render 方法进行渲染的时候,要对函数名用html 标签进行包裹,形成一个自闭合标签。
ReactDOM.render( <App />, document.getElementById('root') );
script 内容更改如下,可以看到页面上输出sam jason
<script type="text/babel"> // welcome 类式组件。 class Welcome extends React.Component { render() { return <h1> {this.props.name}</h1> } } // 组件的组合 function App() { return ( <div> <Welcome name="Sam" /> <Welcome name="Jason" /> </div> ); } ReactDOM.render( <App />, //自闭合标签 document.getElementById('root') ); </script>
这时有两点需要注意:
所有的组件名必须大写,以和普通的html 标签进行区分
所有的组件都返回一个单一的根节点,这也是上面的render 方式中把所有元素都放到一个div 元素中的原因。
使用class声明组件有一个好处,就是组件内部可以有自己的状态。给组件添加一个内部状态,用的是类的构造函数,因为构造函数保存的就是实例身上的属性,可以看成这个实例(组件)的状态, 我们组件也可以使用这个状态
<script type="text/babel"> class Clock extends React.Component { constructor(props){ super(props); this.state = {date: new Date()} // 给组件添加状态 } render() { return ( <div> <h1>Hello World!</h1> <h2>现在是{this.state.date.toLocaleTimeString()} </h2> {/* 使用组件中状态*/} </div> ) } } // 渲染成真实的DOM ReactDOM.render( <Clock />, document.getElementById('root') ); </script>
在这里一定要注意调用构造函数时,super(),这是es6 的语法规定, 子类中没有this, 只能调用super生成子类的 this,如果在调用super 之前使用this, 就会报错。 这个地方其实是不会变化的,可以看成一个模式。每次给组件添加状态的时候,我们就按照这个模式书写就可以了。先写
constructor(props) {
super(props);
}
要添加什么状态,直接在构造函数里面super下面写this.state = …. 就可以了。
constructor(props){ super(props); this.state = {date: new Date()} // 给组件添加状态 }
这里还要涉及到jsx 中的注释,就是在render 方法中添加注释。要用{/* */}方式, {/* 要添加的注释*/}
组件有状态, 这涉及到组件的生命周期,react 定义了非常完善的生命周期函数, 这时也简单地看一下。组件渲染到页面中叫挂载(mounting), 所以渲染完成后,叫做componentDidMount, 组件的卸载叫Unmount, 所以组件将要卸载 叫做componentWillUnmount。我们想要在什么时候使用状态,就可以直接调用生命周期函数,把想要做的事情写到函数里面,生命周期函数直接写在组件内部,比如,页面渲染完成后时间自动加一秒,这时还要涉及到组件的状态更改。React不允许直接更改状态, 或者说,我们不能给状态(如: date)进行赋值操作, 必须调用组件的setState() 方法去更改状态。这里写一个函数tick 来更改状态
tick() { this.setState({ date: new Date() }) }
面渲染完成后时间自动加一秒, 这里要调用componentDidMount,
componentDidMount () { this.timerId = setInterval( () => this.tick() , 1000) }
整个完整的脚本如下,页面中就可以看到时间在走动
class Clock extends React.Component { constructor(props){ super(props); this.state = {date: new Date()} // 给组件添加状态 } // 更改状态 tick() { this.setState({ date: new Date() }) } // 生命周期函数 componentDidMount () { this.timerId = setInterval( () => this.tick() , 1000) } render() { return ( <div> <h1>Hello World!</h1> <h2>现在是{this.state.date.toLocaleTimeString()} </h2> </div> ) } } ReactDOM.render( <Clock />, document.getElementById('root') );
tick 函数也可以直接写到组件里面,根据es6 class 语法的规定,直接写在类中的函数都会绑定在原型上,所以this.tick可以调用。但要保证this 指向的是我们这个组件,而不是其他的东西, 这也是在setInterval 中使用箭头函数的原因。
React 中给元素添加事件,就像我们给元素添加行内事件一样简单,但这里也有不同的地方,事件名用驼峰命名法onClick, 事件处理函数是函数名,用{} 括起来, <button onClick={handleClick}>点击</button>。
声明组件的方式有两种,对应的事件处理也有两种写法. 函数式声明比较简单,我们直接在函数内部声明一个函数作为事件处理函数,然后在render 方法渲染的时候直接给元素注册就好了。
// Button 组件 function Button() { // 事件处理函数 function handleClick(e){ e.preventDefault(); console.log("clicked") } //注册事件 return <button onClick={handleClick}>点击</button> }
Es6 类式声明比麻烦,主要是因为this绑定问题。我们直接在组件内部写处理函数,和任何函数一样,但是如果函数中有this 的话,this却不指向这个组件。我们写一个组件,点击改变按钮上显示的值。
class Toggle extends React.Component { constructor(props){ super(props); this.state = {isToggleOn: true} // 一个状态 } handleClick() { console.log(this) this.setState(prevState => ({ // 改变状态 isToggleOn: !prevState.isToggleOn })) } render() { {/* 调用事件处理函数*/} return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? "On": "Off"} </button> ) } } ReactDOM.render( <Toggle />, document.getElementById('root') );
在页面中点击可以看到handleClick中的事件处理函数console.log 输出是null, 然后报错了。这主要是由于this 是在函数运行的时候动态绑定的,this.handleClick指向了handleClick 函数,点击的时候,这个函数开始执行,但this 却没有指定,它是在哪个环境下执行, 由于 es6 class 是在严格模式下进行的,所以输出了null.
很显然,我们必须使this指向我们这个组件,改变this指向可以使用两种方法,一种是ES5提供的bind()方法,它的第一个参数就是指定函数中this的,且它返回 一个函数,可以知道,返回的这个函数中this已经写死了,在程序运行的时候也不会变化了。在上面代码的构造函数中加上 this.handleClick = this.handleClick.bind(this)
constructor(props){ super(props); this.state = {isToggleOn: true} this.handleClick = this.handleClick.bind(this) }
另一种是箭头函数,它里面的this继承于包围它的函数,那么我们直接把handleClick 事件处理函数写成箭头函数,里面的this 就指向我们这个组件。
handleClick = () => { this.setState(prevState => ({ isToggleOn: !prevState.isToggleOn })) }
无论使用哪种方法,我们的程序都可以正常运行了。