React 简介
于 2013 年来自 Facebook 开源项目.
和 Angular 不一样的是, React 并不是一个完整的 MVC/MVVM 框架, 它只专注于提供清晰, 直接的 View 视图层解决方案. 它的功能全部以构建组件视图为核心, 并提供类似控制器的函数接口和生命周期函数. 所以在 React 中么有控制器, 没有服务, 没有指令, 没有过滤器等等这些.
Virtual DOM
是 React 中的一个非常重要的概念, 在日常开发中, 前端需要将后端的数据呈现到界面中, 同事还要能对用户的操作提供反馈, 并且作用到 UI 上, 这些操作都离不开操作 DOM, 但是频繁的 DOM 操作会造成极大的资源浪费和引起性能瓶颈. 于是 React 引入了 Virtural DOM. 核心就是在内存上构建虚拟 DOM, 然后计算改变前后的 DOM 区别, 最后用最少的 DOM 操作对 DOM 进行操作再用虚拟 DOM 替换真是 DOM.
DIFF 算法
Virtual DOM 技术使用了 DIFF 算法, DIFF 算法是一个比较计算层次结构区别的算法, 主要是用来计算 DOM 之间的差异.(说实话, 我并不懂 DIFF 算法, 只是知道 Virtual DOM 使用 DIFF 算法实现的.)也许上图会更直观一点:
JSX 语法糖
React 中使用 JSX 语法糖, JSX = JavaScript + xml. 可以让开发者在 JS 文件中写 html 模板, 代码语境不需要来回切换. 比如:
jsx 不能直接运行, 是需要 babel-loader 中的 react 这个 preset 编译. 此处不赘述, 在 React 技术栈从 0 搭建中介绍.
需要注意的是:
- import React, {Component} from 'react';
- import {connect} from "react-redux";
- import {getUserDataAction} from "../../Store/actionCreators";
- import md5 from "md5";
- const P_KEY = 'dmx.top';
- class Login extends Component {
- constructor(props) {
- super(props);
- this.state = {
- "user_name": "",
- "user_pwd": ""
- }
- }
- render() {
- return (
- <div>
- <div className="login">
- <div className="login-wrap">
- <div className="avatar">
- <img src="./uploads/logo.jpg" className="img-circle" alt="" />
- </div>
- <div className="col-md-offset-1 col-md-10">
- <div className="input-group input-group-lg">
- <span className="input-group-addon">
- <i className="fa fa-id-card-o"></i>
- </span>
- <input
- name="user_name"
- type="text"
- className="form-control"
- placeholder="请输入用户名"
- onChange={(e) => this._onInputChange(e)}
- onKeyUp={(e) => this._onInputKey(e)}
- />
- </div>
- <div className="input-group input-group-lg">
- <span className="input-group-addon">
- <i className="fa fa-key"></i>
- </span>
- <input
- name="user_pwd"
- type="password"
- className="form-control"
- placeholder="请输入密码"
- onChange={(e) => this._onInputChange(e)}
- onKeyUp={(e) => this._onInputKey(e)}
- />
- </div>
- <button
- type="submit"
- className="btn btn-lg btn-danger btn-block"
- onClick={(e) => this._onSubmit(e)}
>登 录
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
- // 输入框改变
1) 必须被一个单独的大标签包裹, 比如 div.
2)单标签必须封闭, 比如 img , input 等
3)class 要写成 className, for 要写成 htmlFor
4)HTML 注释不能使用, 只能使用 JS 的注释方法
5)原生标签比如 p,li,div 如果需要自定义属性, 必须加 data - 前缀. 自定义组件不需要.
6)JS 表达式用 {} 胆大括号包裹, 在 jsx 中不能使用 if else 语句, 但是可以使用 conditinal(三元运算符)来替代.
7)可以运行函数: 比如
- // 输入框改变
- _onInputChange (e) {
- let inputValue = e.target.value,
- inputName = e.target.name;
- this.setState({[inputName] : inputValue});
- }
8)如果要在 React 组件中写行内样式, 需要还用双大括号包裹. 比如:
<img src={icon_url.includes(undefined) ? icon : icon_url } style={{border: "1px solid #e0e0e0"}} />
9)可以使用数组, 如果是 jsx 语法糖数组会被自动展开, 但是, React 会提示需要给展开的每一项加一个独特的 key 值, 因为底层 DIFF 比对是根据 key 值来比对的. 比如:
- let arr = [1,2,3,4,5,6,7,8,9,10];
- return (
- <div>
- <ul>
- {arr}
- </ul>
- </div>
- );
React 中数据的传递
React 中数据传递三兄弟: state/props/context
三基友之一: state
React 把组件看成一个状态机(State Machines). 通过与用户交互, 实现不同状态, 然后渲染 UI, 让用户界面和数据保持一致.
在 React 中, 只有更新三兄弟中的任何一个, 才会引发 Virtual DOM 的改变, 才能重新渲染用户界面.
- import React from "react";
- class App extends React.Component{
- constructor(){
- super();
- this.state = {
- a : 100,
- b : 200,
- c : 300
- }
- }
- add(){
- this.setState({a : this.state.a + 1});
- }
- render(){
- return (
- <div>
- <h1 > 我是 App 组件</h1>
- <p > 我有状态 state</p>
- <p>a : {this.state.a}</p>
- <p>b : {this.state.b}</p>
- <p>c : {this.state.c}</p>
- <p>
- <input type="button" value="按我" onClick={(this.add).bind(this)}/>
- </p>
- </div>
- )
- }
- }
- // 向外暴露
- export default App;
所以,
定义 state: 在构造函数 (即 constructor) 中使用 this.state 属性即可.
使用 state: 在 JSX 中{this.state.a}
改变 state: this.setState({a: this.state.a + 1}); 不能写成 a: this.state.a++, 因为 state 的属性只读, 必须通过 setState 来改变, 才能确保 state 在页面中安全.
state 的是内部的, 也被称为 local state, 只有组件自己才能改变自己的 state, 别人想改变自己的 state 都不可能.
三基友之二: props
就是定义在自定义组件标签上面的值, 就是 props. 当 props 改变的时候, 会引发 Virtual DOM 的改变, 从而引发视图的重绘. react 崇尚数据的单向流动, 所以设计的时候就是让数据从父组件流向子组件. props 在子组件中是只读的, 不能修改的.
如果父组件想和子组件通信, 就需要使用自身属性往下传递需要通信的数据.
父组件:
- import React from "react";
- import MyCompo from "./MyCompo.js";
- class App extends React.Component{
- constructor(){
- super();
- }
- render(){
- return (
- <div>
- <MyCompo a="66" b="77" c="88"></MyCompo>
- </div>
- )
- }
- }
- // 向外暴露
- export default App;
子组件:
- import React from "react";
- class MyCompo extends React.Component{
- constructor(){
- super();
- }
- render(){
- return (
- <div>
我是 MyCompo 组件
- {/* 子组件中就可以使用 this.props 来枚举父组件传下来的值 */}
- <p>{this.props.a}</p>
- <p>{this.props.b}</p>
- <p>{this.props.c}</p>
- </div>
- );
- }
- }
- // 向外暴露
- export default MyCompo;
如果是要在构造函数中使用父组件传下来的 props, 此时 React 内部会将 props 作为构造函数的第一个参数传进来
- class MyCompo extends React.Component{
- constructor(props){
- super();
- this.state = {
- // 接收系统注入的父组件的属性值为自己的 local 状态
- c : props.c
- }
- }
- }
注意: props 在子组件中, 依然是只读的. 不能修改. 如果想要修改, 只能用 state 来接受, 然后修改 state.
有时候难以避免, 子组件非要和父组件通信, 那只能通过父组件向下传递一个函数, 子组件通过传参的形式, 调用父组件的函数, 然后将数据返回给父组件的函数, 父组件的函数接收到实参之后, 改变父组件自己内部的 state.
- import React from "react";
- import MyCompo from "./MyCompo.js";
- class App extends React.Component{
- constructor(){
- super();
- this.state = {
- d : 16
- }
- }
- setD(number){
- this.setState({"d" : number});
- }
- render(){
- return (
- <div>
- <p > 我是 App 组件, 我有一个 d 状态:{this.state.d}</p>
- <MyCompo setD={(this.setD).bind(this)} d={this.state.d}></MyCompo>
- </div>
- )
- }
- }
- // 向外暴露
- export default App;
子组件就要接受父组件传来的 d 参数和设置 D 的函数:
- import React from "react";
- import { PropTypes } from "prop-types";
- class MyCompo extends React.Component{
- constructor(props){
- super();
- this.state = {
- d : props.d
- }
- this.add = () =>{
- this.setState({"d" : this.state.d + 1});
- props.setD(this.state.d + 1);
- }
- }
- render(){
- return (
- <div>
- <hr/>
我是 MyCompo 组件
- <p>d : {this.state.d}</p>
- <p>
- <input type="button" value="按我更改 d 的值" onClick={this.add}/>
- </p>
- </div>
- );
- }
- }
- // 定义组件需要传入的参数
- //props 属性是可以被验证有效性的, 也就是说规定你要传给我什么样的类型, 就必须是什么样的类型. 否则无效.
- // 需要 yarn add prop-types -D
- MyCompo.propTypes = {
- a : PropTypes.string.isRequired,
- b : PropTypes.string.isRequired,
- c : PropTypes.number.isRequired
- };
- // 向外暴露
- export default MyCompo;
目前看起来还可以接收, 但是如果要和孙子, 重孙子组件通信, 怎么办呢, 虽然可以一层一层的通过 props 往下传递, 但是组件嵌套太深, 既难以维护, 编写有麻烦. 所以将会在后续的文章中介绍 redux, 状态管理.
Tips: 如果通过 props 传递引用类型数据, 此时也是不会颠覆 "数据单向传递" 的限制. 子组件中对数组, JSON 对象的改变, 不会引起父组件中那个数组, JSON 对象的改变, 可以认为传入了副本.
三基友之末: context
context 又称上下文, 其精髓是可以跨级传递数据, 爷爷组件可以直接传递数据到孙子组件.
爷爷组件(哈哈哈哈)
- import React from "react";
- import Baba from "./Baba.js";
- import PropTypes from "prop-types";
- // 为了方便理解, 就用拼音啦.
- class Yeye extends React.Component{
- constructor(){
- super();
- this.state = {
- a : 100
- }
- }
- render(){
- return (
- <div>
- <h1 > 爷爷</h1>
- <Baba></Baba>
- </div>
- );
- }
- // 得到孩子上下文, 实际上这里表示一种设置, 返回一个对象, 这个对象就是现在这个家族体系共享的上下文. 将上下文中的 a 值变为自己的状态中的 a 值
- getChildContext(){
- return {
- a : this.state.a
- }
- }
- }
- // 设置 child 的上下文类型
- Yeye.childContextTypes = {
- a : PropTypes.number.isRequired
- }
- export default Yeye;
孙子组件(哈哈哈哈)
- import React from "react";
- import PropTypes from "prop-types";
- class Sunzi extends React.Component{
- //React 会将上下文当做构造函数的第二个参数传入:
- constructor(props,context){
- super();
- console.log(context); // 得到上下文
- }
- render(){
- return (
- <div>
- <h1 > 孙子</h1>
- </div>
- );
- }
- }
- // 设置上下文的类型
- Sunzi.contextTypes = {
- a : PropTypes.number
- }
- export default Sunzi;
结论:
1) 当祖先元素中更改了上下文的数据, 此时所有的子孙元素中的数据都会更改, 视图也会更新;
2) 反之不成立, 可以认为上下文的数据在子孙元素中是只读的. 此时又要需要使用奇淫技巧, 就是在 context 中共享一个操作祖先元素的函数, 子孙元素通过上下文获得这个函数, 从而操作祖先元素的值. 也就是说, state 是自治的不涉及传值的事儿; props 是单向的, 父亲→儿子; context 也是单向的, 祖先→后代. 如果要反向, 就要传入一个函数.
基本很少使用这个 API, 但是 Redux 底层使用 context 实现.
来源: http://www.bubuko.com/infodetail-3103495.html