React---JSX,组件,Props,State

236 阅读10分钟

JSX语法

ReactDOM.render(h1> hello react /h1>, document.getElementById('root')); //render函数:

//参数二,获取插入到界面的哪个容器

//参数一, 是一个标签//标签也可以作为函数的参数吗?

JSX语法:JavaScript + XML语法(HTML) 解读jsx语法:在react中遇到<>按照HTML语法解析,遇到{}按照JavaScript解析

1、JSX介绍

全称: JavaScript XML, React 使用 JSX 来替代常规的 JavaScript。

JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。

我们不需要一定使用 JSX,但它有以下优点:

  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  • 它是类型安全的,在编译过程中就能发现错误。
  • 使用 JSX 编写模板更加简单快速。
  • 作用: 用来创建react虚拟DOM(元素)对象

    • var ele =

      Hello JSX!

      ;
    • 注意1: 它不是字符串, 也不是HTML/XML标签
    • 注意2: 它最终产生的就是一个JS对象
  • 标签名任意: HTML标签或其它标签

  • 标签属性任意: HTML标签属性或其它

  • 基本语法规则

    • 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
    • 遇到以 { 开头的代码,以JS的语法解析: 标签中的js代码必须用{ }包含
  • babel.js的作用

    • 浏览器的js引擎是不能直接解析JSX语法代码的, 需要babel转译为纯JS的代码才能运行
    • 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

2、使用 JSX

JSX 看起来类似 HTML ,我们可以看以下实例:

html文件:

  <body>
    <div id="root"></div>
  </body>

js文件:

ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
);

注意:代码中能够嵌套多个 HTML 标签,但需要使用一个标签元素包裹它

  • 错误例子:
ReactDOM.render(
  <h1>这是错误的例子</h1>
  <span>假设这里是标题下面的内容</span>,
  document.getElementById("example")
);
  • 正确例子:
ReactDOM.render(
  <section>
    <h1>这是正确的例子</h1>
    <span>假设这里是标题下面的内容</span>
  </section>,
  document.getElementById("example")
);

3、独立文件

可以将React JSX 代码可以放在一个独立文件上,并在不同的js文件中引入即可

AppTop.js文件中:

//定义导出的标签
let AppTop =(<div>
                <h1>我是公共标题栏</h1>
                <p>6666</p>
            </div>);
    
export default AppTop; //将自定义的DOM元素暴露出来,供给其他的页面使用

index.js文件中:

import React from 'react';
import ReactDOM from 'react-dom';
import AppTop from './js/AppTop.js';
​
ReactDOM.render(
    AppTop, 
    document.getElementById('root')
);

4、样式

React 推荐使用内联样式。React 会在指定元素数字后自动添加 px

var myStyle = {
    fontSize: 100,  //等价于fontSize: '100px',
    color: '#00ff00'
};
ReactDOM.render(
    <h1 style = {myStyle}>我是标题</h1>,
    document.getElementById('example')
);

5、注释

注释需要写在花括号中,需要注意的是:

  • 在标签内部的注释需要花括号
  • 在标签外的的注释不能使用花括号
ReactDOM.render(
    /*标签外部的注释 */
    <h1>我是标题 {/*标签内部的注释*/}</h1>,
    document.getElementById('example')
);

6、JavaScript 表达式

我们可以在 JSX 中使用 JavaScript 表达式。表达式写在花括号 {} 中。实例如下:

var a=20
function fn(){return "hello"}
ReactDOM.render(
    <div>
      <h1>{a+1}</h1>
      <h3>{fn()}</h3>
    </div>,
    document.getElementById('example')
);

7、条件渲染

  • 三目运算符

在 JSX 内部不能使用 if else 语句,但可以使用 三元运算 表达式来替代。

var i = 10;
ReactDOM.render(
    <div>
      <h1>{i == 1 ? 'True!' : 'False'}</h1>
      {i > 0 ? <div className="box"></div> : '' }  
    </div>,
    document.getElementById('example')
);
  • 可以在JSX外部使用if语句
var i = 10;
if(i > 0){ //使用if语句的好处是:如果条件不满足且不需要渲染组件时不需要写else
    box = <div className="box"></div>;
}
ReactDOM.render(
    <div>
      { box }
    </div>,
    document.getElementById('example')
);
  • 面试)去掉三目运算符的否则: &&
{i > 0 && <div className="box"></div>}

8、数组

JSX 允许在模板中插入数组,数组会自动展开所有成员

var arr = [
  <h1>HTML</h1>,
  <h2>CSS</h2>
];
ReactDOM.render(
  <div>{arr}</div>,
  document.getElementById('example')
);

9、列表渲染

通过数组的map函数

var aGoods=[
    {id:1,title:"潮流女装"},
    {id:2,title:"品牌男装"},
    {id:3,title:"手机电脑"}
]
​
<ul>  
    {   
        aGoods.map((item,index)=>{   
            return ( //通过return返回多个li元素             
                <li key={index}>{item.title}</li> 
            )        
        })    
    }
</ul>

React 组件

React.js 中一切皆组件,用 React.js 写的其实就是 React.js 组件。

我们在编写 React.js 组件的时候,一般都需要继承 React.js 的 Component(类定义)。一个组件类必须要实现一个 render 方法,这个 render 方法必须要返回一个 JSX 元素。但这里要注意的是,必须要用一个外层的 JSX 元素把所有内容包裹起来。返回并列多个 JSX 元素是不合法的。

1、定义单个组件

(1)定义组件
  • 方式1:通过React自定义组件(DOM元素):类定义
import React, {Component} from 'react';
class MyApp extends Component {
}
​
import React from 'react';
class MyApp extends React.Component {
    constructor(props){
        super(props);
        this.msg="hello,react"
    }
    
    render() {
        return (
            <div>
                <div>{this.msg}</div>
                <h2>这是标题2</h2>
                <h3>这是标题2</h3>
            </div>
        );
    }
}
export default MyApp;
  • 方式2:通过React自定义组件(DOM元素):函数定义
import React from 'react';
//箭头函数
const MyApp = () => <div><p>这是一个段落</p></div>;
export default MyApp;
​
​
//普通函数
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
export default Welcome;
  • 方式3:不建议使用,React.createClass创建组件的方式在react 16版本中去除。
var MyApp = React.createClass({
  render: function() {
    return <h1>Hello World!</h1>;
  }
});
(2)使用组件
import React from 'react';
import ReactDOM from 'react-dom';
import MyApp from './js/MyApp.js'; //导入自定义组件ReactDOM.render(<MyApp />, document.getElementById('root'));

2、定义复合组件

我们可以通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离

import React from 'react';
import ReactDOM from 'react-dom';
​
class WebSite extends React.Component {
    render() {
        return (
            <div>
                <Name name={this.props.name} />
                <Link site={this.props.site} />
            </div>
        );
    }
}
//局部组件Name
class Name extends React.Component {
    render() {
        return (
            <h1>{this.props.name}</h1>
        );
    }
}
//局部组件Link
class Link extends React.Component {
    render() {
        return (
            <h1>{this.props.site}</h1>
        );
    }
}
​
ReactDOM.render(
    <WebSite name="百度一下,你就知道" site='http://www.baidu.com' />, 
    document.getElementById('root')
)

React Props

React 的一大特点是单向数据流。React 中的每一个组件,都包含有一个属性(props),属性主要是从父组件传递给子组件的,在组件内部,我们可以通过this.props获取属性对象。

1、定义和使用props 传值

  • 通过React类定义组件时:

    • 在父组件render 方法中调用组件时使用key/value 的形式来指定属性。
    • 在自定义子组件中通过this.props.key 来获得组件属性的值,需要使用{}括起来。
// 类定义组件时,使用属性 this.props.属性名称
class MyApp extends React.Component {
    render() {
        return (<p>{this.props.name}</p>);
    }
}
​
ReactDOM.render(
    <MyApp name="张三" />, 
    document.getElementById('root')
);
  • 通过React函数定义 组件时:

    • 在父组件render 方法中调用组件时使用key/value 的形式来指定属性。
    • 在自定义子组件中通过函数接收参数propsprops.key来获得组件属性的值,需要使用{}括起来
// 函数定义组件时,在组件内部使用属性值:props.属性名称
function Welcome(props) { // 函数需要传递一个参数props
    return(<h2>{props.title}</h2>)
}
​
ReactDOM.render(
    <Welcome title="张三" />, 
    document.getElementById('root')
);

练习:定义多层嵌套组件(和)实现react 官网头部

2、默认Props

  • 定义默认props
class MyApp extends React.Component {
    render() {
        return <p>this is my {this.props.name}</p>
    }   
}
//由于是用ES6 class语法创建组件,其内部只允许定义方法,而不能定义属性,class的属性只能定义在class之外。所以defaultProps要写在组件外部。
MyApp.defaultProps = {
    name: 'xxx'
};
    
​
ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

3、多属性传值

(1)定义一个this.props对象,在对象中声明多个键值对,用于表示组件的属性

(2)在组件中使用 {...this.props} 的方式传递属性。“...”表示JSX的延展操作符,这种方式可以很方便的为组件指定多个属性,并且为属性的值指定数据类型。

class MyApp extends React.Component {
    render() {
        return(
            <h1>{this.props.name} : {this.props.age} : {this.props.sex}</h1>
        );
    }
}
​
​
​
​
let p1 = {
    name: '张三', 
    age: 18,
    sex: '男'
};
​
ReactDOM.render(
    //<MyApp name='张三' age='18' sex='男' />
    <MyApp {...p1}/>,
    document.getElementById('root')
);

练习:渲染评论列表

数据:

commentList:[
    {id:1,user:"张三",content:"哈哈,沙发"},
    {id:2,user:"李四",content:"哈哈,板凳"},
    {id:3,user:"王五",content:"哈哈,凉席"},
    {id:4,user:"赵六",content:"哈哈,砖头"},
    {id:5,user:"田七",content:"哈哈,楼下"}
]

React State

React 的核心思想是组件化的思想,应用由组件搭建而成,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。

1、如何定义State

定义一个合适的State,是正确创建组件的第一步。State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来;同时,State还必须是代表一个组件UI呈现的最小状态集,即State中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。

组件中用到的一个变量是不是应该作为组件State,可以通过下面的4条依据进行判断:

  1. 这个变量是否是通过Props从父组件中获取?如果是,那么它不是一个状态。
  2. 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
  3. 这个变量是否可以通过其他状态(State)或者属性(Props)计算得到?如果是,那么它不是一个状态。
  4. 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性。

请务必牢记,并不是组件中用到的所有变量都是组件的状态!

class LikeButton extends React.Component {
    //声明一个状态liked   使用this.state.liked获取状态的值
    constructor(props) {
        super(props)
        this.state={liked: false};
    }   
    handleClick(event) {
        this.setState({liked: !this.state.liked});
    }
    render() {
        return (
            <button onClick={this.handleClick}><strong>{this.state.liked ? '喜欢' : '不喜欢'}</strong>我,点我切换
            </button>
        )
    }
}
​
ReactDOM.render(
    <LikeButton />,
    document.getElementById('root')
)

注意:React在ES6的实现中,规定state在constructor中实现

正确定义State的方式如下:

(1)在constructor中实现state

(2)在constructor中通过bind绑定事件函数(事件函数是用来改变状态)

(3)在事件函数内部使用setState函数更改状态

(4)在组件中的render函数中使用该状态

(5)在组件上需要设置监听事件,去触发事件函数的执行

//定义组件
class LikeButton extends React.Component {
    //constructor表示构造器,在constructor需要声明状态state,在声明state之前需要使用super(props);
    constructor(props) {
        super(props);//使用父类的属性
        
        //声明状态
        this.state = {
            liked: false
        }
        //Currently, you are calling bind. 
        //But bind returns a bound function. 
        //You need to set the function to its bound value.
        //目前,你正在调用绑定。但是绑定返回绑定函数。您需要将函数设置为其绑定值。
        this.handleClick = this.handleClick.bind(this);
    }
    
    handleClick(event) {
        this.setState({liked: !this.state.liked});
    }
    
    render() {
        return (
            <button onClick={this.handleClick}>
                你<strong>{this.state.liked ? '喜欢' : '不喜欢'}</strong>我,点我切换
            </button>
        )
    }
}
​
ReactDOM.render(
    <LikeButton />,
    document.getElementById('root')
)

2、setState设置状态

(1)语法:

setState(object nextState[, function callback])

(2)说明:

  • setState是React事件处理函数中和回调函数中触发UI更新的主要方法。
  • 不能在组件内部通过this.state修改状态,因为该状态会在调用setState()后被替换。
  • setState()不一定是同步的,为了性能提升,React会批量执行state和DOM渲染。
  • setState()总是会触发一次组件重绘,但可在shouldComponentUpdate()中实现一些条件渲染逻辑来解决。
class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            clickCount: 0
        };
​
        this.handleClick = this.handleClick.bind(this);
    }
​
    handleClick(event) {
        this.setState({clickCount: this.state.clickCount + 1});
    }
​
    render() {
        return (
            <h2 onClick={this.handleClick}>
                点击后次数变更: {this.state.clickCount}
            </h2>
        );
    }
}
​
ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

同步设置多个状态时(不常用),可以在setState函数的第二个参数可以传递一个function回调函数,如下:

class MyApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            clickCount: 0, 
            isRed: false,
            smallFont: true
        };
​
        this.handleClick = this.handleClick.bind(this);
    }
​
    handleClick(event) {
        this.setState(
            {clickCount: this.state.clickCount + 1},
            function() {
                this.setState(
                    {isRed: !this.state.isRed}, 
                    function() {
                        this.setState({smallFont: !this.state.smallFont});
                    }
                );
            }
            
        );
        console.log(this.state.isred);
    }
​
    render() {
        var redStyle = {color: 'red', fontSize: 50};
        var blueStyle = {color: 'blue', fontSize: 14};
        return (
            <h2 onClick={this.handleClick} style={this.state.isRed ? redStyle : blueStyle}>
                点击后次数变更: {this.state.clickCount}
            </h2>
        );
    }
}
​
ReactDOM.render(
    <MyApp />,
    document.getElementById('root')
);

状态上移: 当存在多个组件共同依赖一个状态,或是当子组件的props 数据需要被修改时,将这个状态放到对应组件的父组件中:

//子组件
class Site extends React.Component {
    render() {
        return (
            <div>
                <button onClick={this.props.updateState}>点击改变</button>
                <h2>{this.props.myData}</h2>
            </div>
        );
    }
}
​
//父组件
class Content extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 'hello'
        };
        this.handleChange = this.handleChange.bind(this);
    }
​
    handleChange(event) {
        this.setState({value: '你好'});
    }
​
    render() {
        return(
            <div>
                <Site myData={this.state.value} updateState={this.handleChange}/>
            </div>
        );
    }
}

3、State 与 Props 区别

除了State, 组件的Props也是和组件的UI有关的。他们之间的主要区别是:

  • props 中的数据都是外界传递过来的
  • state 中的数据都是组件私有的;(通过Ajax 获取回来的数据,一般都是私有数据)
  • props 中的数据都是只读的,不能重新赋值
  • state 中的数据都是可读可写的
  • State定义在constructor内部,在super(props)代码后面;Props默认值定义在类(组件)的外部

当子组件的属性值是可变值时,采用状态上移:

状态上移通过属性将父组件的状态传递到子组件,那么父组件的状态发生变化时,子组件的属性也会改变

4、state/props 实现父子组件通信

  • 子组件获取父组件整个组件进行传参

    • 父组件在调用子组件时,传入一整个组件给子组件 <Children parent={ this } />
    • 父组件中定义一个方法getChildrenMsg(resulet, msg),用来获取子组件传来的值以及执行其他操作
    • 子组件在通过this.props来获取到一整个组件this.props.parent 或者this.props[parent]
    • 子组件调用父组件步骤2里定义的方法进行传值this.props.parent.getChildrenMsg(this,val)

Parent:

import Children from './Children'
export default class Parent extends Component {
    constructor(props) {
        super(props)
        this.state = {
            msg: '父组件传值给子组件',
            childrenMsg: ''
        }
    }
​
    getChildrenMsg = (result, msg) => {
        // console.log(result, msg)
        this.setState({
            childrenMsg: msg
        })
    }
​
    render() {
        return (
            <div>
                <h2>我是父组件 </h2>
                <h3>子组件传来的值为:{ this.state.childrenMsg }</h3>
                <hr/>
                <Children parent={ this } />
                {/*<Children parent={ this.getChildrenMsg.bind(this) } />*/}
            </div>
        )
    }
}

Children:

export default class Children extends Component {
    constructor(props) {
        super(props)
        this.state = {
            msg: '子组件传值给父组件'
        }
    }
​
    toParent = () => {
        // console.log(this.props.parent.getChildrenMsg.bind(this, this.state.msg))
        this.props.parent.getChildrenMsg(this, this.state.msg)
    }
​
    render() {
        return (
            <div>
                <h2>{ 我是子组件 }</h2>
                <button onClick={ this.toParent }>子组件传入给父组件</button>
            </div>
        )
    }
}