React 入门

React入门

包含

  • 是什么

  • 为什么

  • 怎么用

  • 搭建示例应用

不包含

  • 生态圈/公用组件

  • 应用框架

  • 编译/合并代码

是什么

react 是一个做 UI 的库,具体来说是做 UI 组件的库,专注于做 mvc 中的 v.

简单的 REACT 组件

<div id="container">

</div>

<script>
 React.createClass({
    render: function() {
        return <div>Hello {this.props.name}</div>;
    }
});
 
React.render(<Hello name="World" />, document.getElementById('container'));

</script>

用状态控制组件变化

可以把一个组件看做一个状态机, 每一次状态对应于组件的一个 ui

var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },
  tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },
  componentDidMount: function() {//插入真实DOM之后被执行
    this.interval = setInterval(this.tick, 1000);
  },
  componentWillUnmount: function() { //移除DOM之前被执行
    clearInterval(this.interval);
  },
  render: function() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
});

React.render(<Timer />, document.getElementById('container'));

为什么

  • api 少,类库易学

  • 组件内聚,易于组合

  • 原生组件和自定义组件融合渲染

  • 状态/属性驱动全局更新,不用关注细节更新

  • commonjs 生态圈/工具链完善

怎么用

  • jsx

  • event

  • composition

  • props/state

  • mixin

  • form

  • refs

  • component api

  • top api

JSX

类似 xml 的语法,用来描述组件树

<div classname="x">
    <a href="#">#</a>
    <component x="y">1</component>
</div>
React.createElement('div',{
className:'x'
},[
React.createElement('a',{href:'#'},'#'),
React.createElement(Component,{x:'y'},1);
]);

注意和 html 语法不太一样,比如必须是驼峰命名, 以及属性名不能和 js 关键字冲突,例如:className,readOnly

JSX 嵌入变量

可以通过 {变量名} 来将变量的值作为属性值

var x = 'http://www.baidu.com';
var y = <a href= {x} target="_blank">baidu.com</a>;
React.render(y, document.getElementById('container'));

JSX SPREAD

可以用通过 {…obj} 来批量设置一个对象的键值对到组件的属性,注意顺序

var x = 'http://www.baidu.com';
var obj = {
  href:"http://www.taobao.com",
  target:"_blank"
}
var y = <a {...obj} href= {x}>baidu.com</a>;
React.render(y, document.getElementById('container'));

PROPS

通过 this.props 可以获取传递给该组件的属性值,还可以通过定义 getDefaultProps 来指定默认属性值

var B = React.createClass({
    getDefaultProps(){
        return {
            title:'default'
        };
    },
    render(){
        return <b>{this.props.title}</b>
    }
});

React.render(<div>
<B title="指定  "/> <B/>
</div>,document.getElementById('container'));

PROP VALIDATION

通过指定 propTypes 可以校验属性值的类型

var B = React.createClass({
    propTypes: {
        title: React.PropTypes.string,
    },

    getDefaultProps(){
        return {
            title:'default'
        };
    },
    render(){
        return <b>{this.props.title}</b>
    }
});

React.render(<div>
<B title="指定  "/> <B title={2}/>
</div>,document.getElementById('container'));

注意:校验仅为提升开发者体验

STATE

组件内部的状态,可以使用 state

var Timer = React.createClass({
  getInitialState: function() {
    return {secondsElapsed: 0};
  },
  tick: function() {
    this.setState({secondsElapsed: this.state.secondsElapsed + 1});
  },
  componentDidMount: function() {
    this.interval = setInterval(this.tick, 1000);
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  render: function() {
    return (
      <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
    );
  }
});

React.render(<Timer />, document.getElementById('container'));

EVENT

可以通过设置原生 dom 组件的 onEventType 属性来监听 dom 事件,例如 onClick, onMouseDown,在加强组件内聚性的同时,避免了传统 html 的全局变量污染

var LikeButton = React.createClass({
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

React.render(
  <LikeButton />,
  document.getElementById('container'));

注意:事件回调函数参数为标准化的事件对象,可以不用考虑 ie

组合

可以像使用原生 dom 组件一样使用自定义的组件

var A = React.createClass({
    render(){
        return <a href='#'>a</a>
    }
});

var B = React.createClass({
    render(){
        return <i><A /> !</i>;
    }
});

React.render(<B />,document.getElementById('container'));

组合 child

自定义组件中可以通过 this.props.children 访问自定义组件的子节点

var B = React.createClass({
    render(){
        return <ul>
            {React.Children.map(this.props.children,function(c){
                return <li>{c}</li>;
            })}
        </ul>;
    }
});

React.render(<B>
<a href="#">1</a>
2
</B>,document.getElementById('container'));

MIXIN

mixin 是一个普通对象,通过 mixin 可以在不同组件间共享代码

var mixin = {
    propTypes: {
        title: React.PropTypes.string,
    },

    getDefaultProps(){
        return {
            title:'default'
        };
    },
};

var A = React.createClass({
    mixins: [mixin],
    render(){
        return <i>{this.props.title}</i>
    }
});

var B = React.createClass({
    mixins: [mixin],
    render(){
        return <b>{this.props.title}</b>
    }
});

React.render(<div>
<B/> <A title={2}/>
<A/>
</div>,document.getElementById('container'));

FORM

和 html 的不同点:

  1. value/checked 属性设置后,用户输入无效

  2. textarea 的值要设置在 value 属性

  3. select 的 value 属性可以是数组,不建议使用 option 的 selected 属性

  4. input/textarea 的 onChange 用户每次输入都会触发(即使不失去焦点)

  5. radio/checkbox 点击后触发 onChange

控制组件

如果设置了 value 属性,那么改组件变为受控组件,用户无法输入,除非程序改变 value 属性

var Test = React.createClass({
    render(){
        return <input value="x" />
    }
});

React.render(<Test />,document.getElementById('container'));

可以通过监听 onChange 事件结合 state 来改变 input 的值

var Test = React.createClass({
    getInitialState(){
        return {
            value:'xasdasdf'
        };
    },
    onChange(e){
        this.setState({
            value:e.target.value
        });
    },
    render(){
        return <input value={this.state.value} onChange={this.onChange}/>
    }
});

React.render(<Test />,document.getElementById('container'));

不受控制的组件

设置 defaultValue 为设置 input 的初始值,之后 input 的值由用户输入

var Test = React.createClass({
    render(){
        return <input defaultValue="xyz" />
    }
});

React.render(<Test />,document.getElementById('container'));

REF

该功能是为了结合现有非 react 类库,通过 ref/refs 可以取得组件实例,进而取得原生节点

var Test = React.createClass({
    componentDidMount(){
      alert(React.findDOMNode(this.refs.content).innerHTML);
    },
    render(){
        return <div>
            <h3>header</h3>
            <div ref='content'>content</div>
        </div>;
    }
});

React.render(<Test />,document.getElementById('container'));

组件的API

  • React.createClass 定义组件时允许传入的配置

  • getDefaultProps 得到默认属性对象

  • propTypes 属性检验规则

  • mixins 组件间公用方法

组件生命周期

React.createClass 定义时允许传入的函数,会在特定生命周期内调用

初次创建组件时调用

  • getInitialState 得到初始状态对象

  • render 返回组件树. 必须设置

  • componentDidMount 渲染到 dom 树中是调用,只在客户端调用,可用于获取原生节点

组件的属性值改变时调用

  • componentWillReceiveProps 属性改变调用

  • shouldComponentUpdate 判断是否需要重新渲染

  • render 返回组件树. 必须设置

  • componentDidUpdate 渲染到 dom 树中是调用, 可用于获取原生节点

  • 最后是 componentWillUnmount 组件从 dom 销毁前调用

组件生命周期的演示

function log(str){
  document.getElementById('log').innerHTML+='<p>'+str+'</p>';;
}
document.getElementById('clear').onclick=function(){
document.getElementById('log').innerHTML='';
};
var Test = React.createClass({
  getInitialState() {
    log('getInitialState');
    return {
      value: this.props.value
    };
  },
  
  componentWillReceiveProps(nextProps){
    log('componentWillReceiveProps');
    this.setState({
        value: nextProps.value
    });
  },
  
  shouldComponentUpdate(nextProps,nextState){
    log('shouldComponentUpdate');
    return true;
  },
  
  componentWillUpdate(nextProps,nextState){
    log('componentWillUpdate');
  },
  
  componentWillMount(){
    log('componentWillMount');
  },

  render() {
    log('render');
    return <span>{this.props.value}</span>
  },
  
  componentDidMount() {
      log('componentDidMount');
  },
  
  componentDidUpdate(prevProps,prevState) {
      log('componentDidUpdate');
  },
  
  componentWillUnmount(prevProps,prevState) {
      log('componentWillUnmount');
  }
});


var Hello = React.createClass({
    getInitialState() {
      return {
        value:1,
        destroyed:false
      };
    },
    increase() {
        this.setState({
            value: this.state.value+1
        });
    },
    destroy() {
        this.setState({
            destroyed: true
        });
    },
    render: function() {
        if(this.state.destroyed){
            return null;
        }
        return <div>
        <p>
          <button onClick={this.increase}>increase</button>
          <button onClick={this.destroy}>destroy</button>
        </p>
        <Test value={this.state.value}/>
        </div>;
    }
});
 
React.render(<Hello />, document.getElementById('container'));

TOP API

  • React.createClass 创建组件类

  • React.findDOMNode 从组件实例获取 dom 根节点

  • React.render 渲染组件到 dom

  • React.Children.* 操作 map/forEach children 工具类

一步步搭建一个评论应用

var Comment = React.createClass({
  render: function () {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

var CommentList = React.createClass({
  render: function () {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

var CommentForm = React.createClass({
  getInitialState() {
    return {
      name: '',
      text: ''
    }
  },
  updateField(field, e) {
    var state = {};
    state[field] = e.target.value;
    this.setState(state);
  },
  handleSubmit(e){
    e.preventDefault();
    this.props.onPost({
      author:this.state.name,
      text:this.state.text
    });
    this.setState({
      name:'',
      text:''
    });
  },
  render: function () {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input placeholder="Your name" value={this.state.name} onChange={this.updateField.bind(this, 'name')}/>
        <input placeholder="Say something..."
          value={this.state.text} onChange={this.updateField.bind(this, 'text')}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

var database=[
  {
    author: '作者 1',
    text: '评论 1,' + Date.now()
  },
  {
    author: '作者 2',
    text: ' *评论 2,' + Date.now() + '* '
  }
];

var CommentBox = React.createClass({
  loadCommentsFromServer: function () {
    var self = this;
    $.ajax({
      url: this.props.url,
      method:'post',
      dataType:'json',
      data: {
        json:JSON.stringify({
          data:database
        })
      },
      success(res) {
        self.setState({data: res.data})
      }
    });
  },
  getInitialState: function () {
    return {data: []};
  },
  handlePost(post){
    database.push(post);
    this.loadCommentsFromServer();
  },
  componentDidMount: function () {
    this.loadCommentsFromServer();
  },
  render: function () {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onPost={this.handlePost}/>
      </div>
    );
  }
});



React.render(
  <CommentBox url="/echo/json/" />,
  document.getElementById('container')
);

组件分解

  • 顶层 CommentBox

  • 评论列表 CommentList

  • 单条评论 Comment

  • 评论表单 CommentForm

最简单的 COMMENTBOX

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('container')
);

组合组件

var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('container')
);

属性传递

var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
});

var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        <Comment author="作者 1">评论 1</Comment>
        <Comment author="作者 2">评论 2</Comment>
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('container')
);

使用 DOM 库 MARKED

var Comment = React.createClass({
  render: function() {
  var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        <Comment author="作者 1">评论 1</Comment>
        <Comment author="作者 2"> *评论 2* </Comment>
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('container')
);

数据分离

var data = [
  {author: "作者 1", text: "评论 1"},
  {author: "作者 2", text: "*评论 2*"}
];

var Comment = React.createClass({
  render: function() {
  var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

var CommentList = React.createClass({
  render: function() {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={data}/>
        <CommentForm />
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('container')
);

从服务器取得数据

var Comment = React.createClass({
  render: function () {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

var CommentList = React.createClass({
  render: function () {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function () {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});

var CommentBox = React.createClass({
  loadCommentsFromServer: function () {
    var self = this;
    new Request.JSON({
      url: this.props.url,
      data: {
        json: JSON.encode({
          data: [
            {
              author: '作者 1',
              text: '评论 1,' +Date.now()
            },
            {
              author: '作者 2',
              text: ' *评论 2,'+Date.now()+'* '
            }
          ]
        }),
        delay:1
      },
      onSuccess(res) {
        self.setState({data: res.data})
      }
    }).send();
  },
  getInitialState: function () {
    return {data: []};
  },
  componentDidMount: function () {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function () {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

React.render(
  <CommentBox url="/echo/json/" pollInterval={2000} />,
  document.getElementById('container')
);

评论表单

var Comment = React.createClass({
  render: function () {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

var CommentList = React.createClass({
  render: function () {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

var CommentForm = React.createClass({
  getInitialState() {
    return {
      name: '',
      text: ''
    }
  },
  updateField(field, e) {
  console.log(e);
    var state = {};
    state[field] = e.target.value;
    this.setState(state);
  },
  handleSubmit(e){
    e.preventDefault();
    this.props.onPost({
      name:this.state.name,
      text:this.state.text
    });
    this.setState({
      name:'',
      text:''
    });
  },
  render: function () {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input placeholder="Your name" value={this.state.name} onChange={this.updateField.bind(this, 'name')}/>
        <input placeholder="Say something..."
          value={this.state.text} onChange={this.updateField.bind(this, 'text')}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

var database=[
  {
    author: '作者 1',
    text: '评论 1,' + Date.now()
  },
  {
    author: '作者 2',
    text: ' *评论 2,' + Date.now() + '* '
  }
];

var CommentBox = React.createClass({
  loadCommentsFromServer: function () {
    var self = this;
    $.ajax({
      url: this.props.url,
      method:'post',
      dataType:'json',
      data: {
        json:JSON.stringify({
          data:database
        })
      },
      success(res) {
      console.log(res)
        self.setState({data: res.data})
      }
    });
  },
  getInitialState: function () {
    return {data: []};
  },
  handlePost(){
    
  },
  componentDidMount: function () {
    this.loadCommentsFromServer();
  },
  render: function () {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onPost={this.handlePost}/>
      </div>
    );
  }
});



React.render(
  <CommentBox url="/echo/json/" />,
  document.getElementById('container')
);

最后:通知重新渲染

var Comment = React.createClass({
  render: function () {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

var CommentList = React.createClass({
  render: function () {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

var CommentForm = React.createClass({
  getInitialState() {
    return {
      name: '',
      text: ''
    }
  },
  updateField(field, e) {
    var state = {};
    state[field] = e.target.value;
    this.setState(state);
  },
  handleSubmit(e){
    e.preventDefault();
    this.props.onPost({
      author:this.state.name,
      text:this.state.text
    });
    this.setState({
      name:'',
      text:''
    });
  },
  render: function () {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input placeholder="Your name" value={this.state.name} onChange={this.updateField.bind(this, 'name')}/>
        <input placeholder="Say something..."
          value={this.state.text} onChange={this.updateField.bind(this, 'text')}
        />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

var database=[
  {
    author: '作者 1',
    text: '评论 1,' + Date.now()
  },
  {
    author: '作者 2',
    text: ' *评论 2,' + Date.now() + '* '
  }
];

var CommentBox = React.createClass({
  loadCommentsFromServer: function () {
    var self = this;
    $.ajax({
      url: this.props.url,
      method:'post',
      dataType:'json',
      data: {
        json:JSON.stringify({
          data:database
        })
      },
      success(res) {
        self.setState({data: res.data})
      }
    });
  },
  getInitialState: function () {
    return {data: []};
  },
  handlePost(post){
    database.push(post);
    this.loadCommentsFromServer();
  },
  componentDidMount: function () {
    this.loadCommentsFromServer();
  },
  render: function () {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onPost={this.handlePost}/>
      </div>
    );
  }
});



React.render(
  <CommentBox url="/echo/json/" />,
  document.getElementById('container')
);

实现一个 checkbox

功能点

  • 新生成组件/更新组件

  • checked/unchecked 交互处理

  • 键盘

  • label?

var Checkbox = React.createClass({
  getDefaultProps() {
    return {
      defaultChecked: false,
      onChange(){}
    };
  },

  getInitialState() {
    var state = {};
    var props = this.props;
    if('checked' in props){
      state.checked = props.checked;
    } else {
      state.checked = props.defaultChecked;
    }
    return state;
  },

  componentWillReceiveProps(newProps){
    // 组件重新渲染了,属性可能有改变,同步属性到状态
    if('checked' in newProps){
      this.setState({
        checked: newProps.checked
      });
    }
  },

  onClick() {
    var nextChecked = !this.state.checked; 
    if(!('checked' in this.props)){
      // 非受限
      this.setState({
        checked: nextChecked
      });
    }
    // 回调函数通知外部
    this.props.onChange(nextChecked);
  },

  render() {
    var state = this.state;
    var style = {border:'1px solid red',display:'inline-block',width:100,height:100}
    if(state.checked){
      style.backgroundColor='red';
    }
    return <span style={style} onClick={this.onClick}></span>;
  }
});



var Test = React.createClass({
    getInitialState(){
        return {
            checked: false
        };
    },
    onChange(){
        this.setState({
        checked: !this.state.checked
        });
    },
    render(){
        return <div>
        <p>受限: <Checkbox checked={this.state.checked} /></p>
        <p>不受限: <Checkbox defaultChecked={true} onChange={this.onChange}/></p>
        </div>;
    }
});
React.render(<Test />, document.getElementById('container'));

说明:from yiminghe

    原文作者:嘉伦
    原文地址: https://segmentfault.com/a/1190000007419958
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞