前端社团React.jsReact Native开发经验集

React学习 & 与Vue的比较总结

2017-07-18  本文已影响154人  ccminn

React文档学习

学习资源:react中文文档

前言

有过Vue与Vuex的学习基础;
在重新学习React的时候,与过去的Vue开发经历进行了简单比较,写下了这篇笔记

总结

重点:摆正心态
过去使用双向绑定的Vue进行开发,虽然对于Vue与Vuex的使用也不过皮毛,不过也确实感觉到了其中的简便;而对于React的第一印象便是数据单项绑定,一开始便有了畏难情绪。在这一次的文档学习中,理解了React所谓的单向数据流思想,感觉从某些角度看,它的存在还是比Vue有着一定优势的,因此调整心态还是挺重要的。
明天继续学习Redux,并且与Vuex进行比较。

目录

1、JSX文件类型:融合了js与html两种语言

const element = <h1>Hello, world!</h1>;

html段中可包含js语法

// 调用函数
function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!
  </h1>
);


// 设置标签属性
const element = <img src={user.avatarUrl}></img>;

在js中处理html段

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

// 闭合式标签需要在结尾加/
const element = <img src={user.avatarUrl} />;

防止注入式攻击:
推荐嵌套写入数据:

const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
// 完整示例
// 每秒更新html段
// react仅仅更新必要的部分,也就是变量部分
// 由此体现Virtual DOM tree的高效
function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);  // 每过一秒,重新传入render函数

2、渲染

const element = <h1>Hello, world</h1>;
// 传入ReactDOM.render方法进行渲染
ReactDOM.render(
  element,
  document.getElementById('root')
);

react元素在创建后是不可变的,更新的唯一方法是创建新元素,然后传入ReactDOM.render方法


3、组件与依赖

props属性可读,可用,不可修改

渲染

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// react会将jsx属性作为一个对象props,传递给组件实例
// 组件的name属性必须大写开头
const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

多次渲染

// 多次渲染该组件时
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}


function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

组件的提取,细化,提高可复用性

// 提取前的comment组件,混乱,层次复杂
function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}


// 提取后的comment组件
function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}


4、组件状态的作用

以时钟为例 ===> 利用组件状态优化

// 按照以下写法,需要对Clock实例添加单独的实现,而不是属于Clock组件的自身方法,需要多次渲染Clock实例
// 理想状态下,应该是渲染一次(调用ReactDOM.render())即可,生成的Clock实例会更新自身
function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

5、组件状态与生命周期

状态(state):组件的私有属性,是局部的,完全受控于当前组件;
属性(JSX属性):属性是被传入的,只可读不可修改的;
生命周期:在重新渲染组件前,释放组件所占用的资源(DOM、计时器、eventListener)
函数组件 ==> 类 :使组件拥有状态,拥有生命周期

// 1、扩展为 React.Component 的ES6 类
class Clock extends React.Component {
    // 2. 添加类构造函数来初始化状态this.state,用来替代原本的props
  constructor(props) {
    super(props);    // 类组件应始终使用props调用基础构造函数。
    this.state = {date: new Date()};   // 不再需要从外部传入jsx属性
  }

  // 2、 将函数组件中的函数体移动到render方法中
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }


  // 3、添加生命周期钩子
  // 当组件输出到 DOM 后会执行 componentDidMount() 钩子
  // 挂载  当dom中添加了该元素,就挂载定时器,用于更新时间
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick()
      ,1000);
  }
    
  // 在卸载组件时,连同计时器一并卸载
  // 在卸载组件前执行componentWillUnmount()函数
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
    
  // tick方法:调用setState更新组件的局部状态,来调度UI更新
  tick() {
    this.setState({
      date: new Date()
    });
  }
}



// 最终达到我们的理想效果,一次渲染即可
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

关于setState()

  1. 通过对象属性更新,并不会重新渲染
this.state.comment = 'Hello';   // 此代码不会重新渲染组件
// 应当使用setState
  1. 状态更新可能是异步的
    react可将多个setState()合并;所以不要依赖他们的只进行计算……可能用到的是脏值
// 此代码无法用于实时更新的state,因为具体的set时间未知…… 
this.setState({
  counter: this.state.counter + this.props.increment,
});  
// 对应修复的方法:**向setState传入函数**,而不是对象   
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));
  1. 状态合并更新
constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }
this.setState({comments})   // 保留this.state.posts,但替换了this.state.comments

自顶向下单项数据流: 也就是react的特色(与vue的双向通信大不相同)

任何状态始终由某些特定(上层)组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。


6、事件处理

事件属性:驼峰式命名法
jsx语法:传入函数作为事件处理函数,如:onclick={activate};而不是字符串形式,如onclick="activateLasers()"

类的监听事件默认不会绑定this,因此添加事件绑定时

方法1:需要手动bind(this)

this.handleClick = this.handleClick.bind(this);

方法2:使用箭头函数

handleClick = () => {
    console.log('this is:', this);
  }

7、条件渲染

{unreadMessages.length > 0 &&
  <h2>
    You have {unreadMessages.length} unread messages.
  </h2>
}

8、列表渲染

原始渲染

// 简单粗暴的渲染方法,手动生成html代码段
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

// listItems包含了多个li标签
ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

优化列表组件渲染

// 创建基础列表组件   只需传入列表数组,即可自动生成
// ## 使用li标签必须添加一个key值,主要用于在li的一系列标签中唯一标识该元素,用于后续的删除、修改操作
// key的作用域是同级节点之间 
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number,index) => 
    // 当数组元素没有确定的id用于标识,可以用index替代
    <li key={index}>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);


9、状态提升

目的:用于父组件中使用多个子组件,则将子组件之间的共享状态提升到父组件中进行管理
问题:子组件之间的共享状态state保存到父组件中,子组件通过props读取,但是不能修改;
解决途径:为了使子组件能够修改提升后的共享状态,将父组件的方法作为props属性值传入子组件来实现;子组件借用父组件的方法来修改父组件的state,“虽然很麻烦,但是有助于查找bug”,在今后的实践中检验一下这句官网的话....

变量提升的简单实例


10、终极实例

“假双向通信”实例的逐步实现

在看完这一个代码之后,可以很明显的感觉到Vue与React的不同;React更加注重细化提取子组件,在单文件中定义一个多级组件,而Vue仅仅定义单个组件,然后引用其他....
而react的组件代码虽然真的很长很长,但是耐心的看来下,结构还是比较清晰的,重要的是要先分清组件之间的层级关系,还有共享数据的存放位置;

在以下这一个“假双向通信”的组件中,包含组件的层级关系表现为 :

   FilterableProductTable   
           /\  
          /  \  
 SearchBar    ProductTable  
                  /\  
                 /  \  
ProductCategoryRow   ProductRow  

其中FilterableProductTable是顶级组件,保存state,将setState分发给SearchBar;而ProductTable以及它下属的ProductCategoryRow与ProductRow仅仅是获取state进行数据更新

class ProductCategoryRow extends React.Component {
  render() {
    return (<tr><th colSpan="2">{this.props.category}</th></tr>);
  }
}

class ProductRow extends React.Component {
  render() {
    var name = this.props.product.stocked ?
      this.props.product.name :
      <span style={{color: 'red'}}>
        {this.props.product.name}
      </span>;
    return (
      <tr>
        <td>{name}</td>
        <td>{this.props.product.price}</td>
      </tr>
    );
  }
}

class ProductTable extends React.Component {
  render() {
    var rows = [];
    var lastCategory = null;
    console.log(this.props.inStockOnly)
    this.props.products.forEach((product) => {
      if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
        return;
      }
      // 为不同的category创建一个ProductCategoryRow,每遍历一次都为当前category的所有product创建一个新ProductRow
      // ProductCategoryRow与ProductRow 属于平级元素,都是ProductTable的td标签元素
      if (product.category !== lastCategory) {
        rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
      }
      rows.push(<ProductRow product={product} key={product.name} />);
      lastCategory = product.category;
    });
    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Price</th>
          </tr>
        </thead>
        <tbody>{rows}</tbody>
      </table>
    );
  }
}

class SearchBar extends React.Component {
  constructor(props) {
    super(props);
    this.handleFilterTextInputChange = this.handleFilterTextInputChange.bind(this);
    this.handleInStockInputChange = this.handleInStockInputChange.bind(this);
  }

  handleFilterTextInputChange(e) {
    this.props.onFilterTextInput(e.target.value);
  }

  handleInStockInputChange(e) {
    this.props.onInStockInput(e.target.checked);
  }

  render() {
    return (
      <form>
        <input
          type="text"
          placeholder="Search..."
          value={this.props.filterText}
          onChange={this.handleFilterTextInputChange}
        />
        <p>
          <input
            type="checkbox"
            checked={this.props.inStockOnly}
            onChange={this.handleInStockInputChange}
          />
          {' '}
          Only show products in stock
        </p>
      </form>
    );
  }
}

class FilterableProductTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      // 关键字搜索
      filterText: '',
      // checkbox状态
      inStockOnly: false
    };

    this.handleFilterTextInput = this.handleFilterTextInput.bind(this);
    this.handleInStockInput = this.handleInStockInput.bind(this);
  }

  handleFilterTextInput(filterText) {
    this.setState({
      filterText: filterText
    });
  }

  handleInStockInput(inStockOnly) {
    this.setState({
      inStockOnly: inStockOnly
    })
  }

  render() {
    return (
      <div>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
          onFilterTextInput={this.handleFilterTextInput}
          onInStockInput={this.handleInStockInput}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
}


var PRODUCTS = [
  {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
  {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
  {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
  {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
  {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
  {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
];

ReactDOM.render(
  <FilterableProductTable products={PRODUCTS} />,
  document.getElementById('container')
);

上一篇下一篇

猜你喜欢

热点阅读