美文网首页
Get Strated of React

Get Strated of React

作者: lndyzwdxhs | 来源:发表于2018-06-01 10:14 被阅读21次

    痛点:前端html的开发越来越复杂,没有可重用和模块化编程

    • React不是一个完整的MVCMVVM框架,它只负责View
    • 基于Virtual DOM思想

    0x01 JSX & Components

    • 类似于DOM结构的对象,html标签默认小写,自定义的组件(Components)首字母大写
    • jsx中需要执行js表达式时,需要使用中括号({})包起来
    • jsx的样式不能使用class表示,因为在jsclass是关键字,需要使用clasName表示
    • jsx内的style需要使用对象来表示,属性使用驼峰式命名规则
    // 默认jsx
    <div> Hello Word! </div>
    // 自定义组件
    <Hello className='xxxx' style={{color:'red',fontSize:'20px'}} name='xxx' />
    
    state & props
    • props是组件调用方在调用组件时传入的,props一旦指定一般不会改变,在结构上,props也是属于调用方
    • state是私属于组件方,state是可变的,即可以理解为函数内部状态参数。通过ComponentsetState方法来修改state的值
    • state是组件内部的状态,state变化的时候会重新render
    componentDisMount : function () {
      var _self = this;
      // 为什么要对this重新赋值?
      // 因为在window.setTimeout()函数参数之外this指的就是当前的component,
      // 回调函数中的this指的就是最外边的全局对象:global或window
      // this的4中用法:
      // 1:构造函数内部this,指的是创建的对象
      function cst(){
        this.x=1;
      };
      var t1 = new cst();
      // 2:call apply bind中使用,传入的this替换函数内部的this
      // 3:this指的是调用函数的那个对象
      // 4:this出现在setTimeout函数的函数内部时,this指的是全局对象
      window.setTimeout(function () {
        _self.setState({
          fontSize:'40px',
        });
      }, 1000);
    }
    
    context
    • 什么场景下使用context:当你不想每层组件都手动传递需要的props的时候
    • 官方建议不要使用context,用props & state代替控制数据流
    • 官方详细参考资料

    使用props传递color属性

    class Button extends React.Component {
      render() {
        return (
          <button style={{background: this.props.color}}>
            {this.props.children}
          </button>
        );
      }
    }
    
    class Message extends React.Component {
      render() {
        return (
          <div>
            {this.props.text} <Button color={this.props.color}>Delete</Button>
          </div>
        );
      }
    }
    
    class MessageList extends React.Component {
      render() {
        const color = "purple";
        const children = this.props.messages.map((message) =>
          <Message text={message.text} color={color} />
        );
        return <div>{children}</div>;
      }
    }
    

    使用context,我们可以自动地在组件树中传递参数。

    const PropTypes = require('prop-types');
    
    class Button extends React.Component {
      render() {
        return (
          <button style={{background: this.context.color}}>
            {this.props.children}
          </button>
        );
      }
    }
    
    Button.contextTypes = {
      color: PropTypes.string
    };
    
    class Message extends React.Component {
      render() {
        return (
          <div>
            {this.props.text} <Button>Delete</Button>
          </div>
        );
      }
    }
    
    class MessageList extends React.Component {
      getChildContext() {
        return {color: "purple"};
      }
    
      render() {
        const children = this.props.messages.map((message) =>
          <Message text={message.text} />
        );
        return <div>{children}</div>;
      }
    }
    
    MessageList.childContextTypes = {
      color: PropTypes.string
    };
    

    通过在MessageListcontext提供者)中添加childContextTypesgetChildContextReact会向下自动传递参数,任何组件只要在它的子组件中(这个例子中是Button),就能通过定义contextTypes来获取参数。
    如果contextTypes没有定义,那么context将会是个空对象。

    0x02 React Components Lifecycle

    Lifecycle
    mounted
    • React ComponentsRender解析生成对应的DOM节点并被插入到浏览器DOM结构的过程,即当我们在浏览器上可以看到组件的时候,Mounted已经结束了
    update
    • 一个MountedReact Components被重新Render的过程,差量变更
    • 4hook函数一般不进行修改
    unmounted
    • 一个MountedReact Components对应的DOM节点被从浏览器DOM结构中移除的过程

    0x03 React Event Listener

    • React Component上绑定事件,采用驼峰式来绑定事件

    0x04 When the project starts to use

    yeoman
    • 用来生成项目代码结构,根据自己选择架构,自动生成项目结构
    webpack
    • 相当于盖好了房子给用户来使用,grunt等工具则是需要用户自己来盖房子
    • Webpack本身只能处理JavaScript模块,如果要处理其他类型的文件,就需要使用loader进行转换。将css文件转换成style属性绑定到DOM上,将图片等url也加到对应的位置

    0x05 Redux

    • React是纯View层框架,需要搭配数据流才能进行完整的前端开发,这个数据流就是Redux
    • 目前的数据流框架:FluxreFluxRedux
    • Redux简答、单一状态树
    Flux单向数据流 Redux数据流
    action
    • 普通的JS对象Objects
    • 一般由方法生成
    • 必须有type属性
    const addTodo = (text) => {
        return {
            type : "ADD_TODO",
            id : nextTodoId++,
            text
        }
    }
    
    reducer
    • 也由方法生成
    • 生成它的方法是一个纯函数(纯方法就是没有任何外界依赖,1+1=2这样的必然性 )
    • 传入旧的state,返回新的state
    const todo = (state, action) => {
        switch (action.type) {
            case "ADD_TODO":
                return {
                    id : action.id,
                    text : action.text,
                    completed : false
                 }
            default:
                return state
        }
    }
    
    store
    • store包含所有的reducer
    • action作用于store上,而不是直接作用于reducer
    • reducer根据store做响应
    • statereducer的混合体
    • store是唯一的
    • 包括了完整的state
    • state完全可预测
    react-redux
    • 项目中可以直接使用redux,也可以使用react-redux,后者提供了便捷的API
    • 组件需要分成两类:UI componentcontainer componentUI组件展示样式,container组件处理业务逻辑、数据和状态
    • 如果一个组件既有 UI 又有业务逻辑怎么办?将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。
    • connect() 用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。

    下面代码中,TodoListUI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。

    import { connect } from 'react-redux'
    const VisibleTodoList = connect()(TodoList);
    

    但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。
    (1)输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
    (2)输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。
    因此,connect方法的完整 API 如下。

    import { connect } from 'react-redux'
    const VisibleTodoList = connect(
      mapStateToProps,
      mapDispatchToProps
    )(TodoList)
    

    上面代码中,connect方法接受两个参数:mapStateToPropsmapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action

    mapStateToProps()

    • 它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系
    • 函数返回一个对象,里面的每一个键值对就是一个映射

    下面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。这个对象有一个todos属性,代表 UI 组件的同名参数,后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值。

    const mapStateToProps = (state) => {
      return {
        todos: getVisibleTodos(state.todos, state.visibilityFilter)
      }
    }
    

    下面就是getVisibleTodos的一个例子,用来算出todos

    const getVisibleTodos = (todos, filter) => {
      switch (filter) {
        case 'SHOW_ALL':
          return todos
        case 'SHOW_COMPLETED':
          return todos.filter(t => t.completed)
        case 'SHOW_ACTIVE':
          return todos.filter(t => !t.completed)
        default:
          throw new Error('Unknown filter: ' + filter)
      }
    }
    

    mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
    mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。

    // 容器组件的代码
    //    <FilterLink filter="SHOW_ALL">
    //      All
    //    </FilterLink>
    
    const mapStateToProps = (state, ownProps) => {
      return {
        active: ownProps.filter === state.visibilityFilter
      }
    }
    

    使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。
    connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。

    mapDispatchToProps()

    mapDispatchToPropsconnect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
    如果mapDispatchToProps是一个函数,会得到dispatchownProps(容器组件的props对象)两个参数。

    const mapDispatchToProps = (
      dispatch,
      ownProps
    ) => {
      return {
        onClick: () => {
          dispatch({
            type: 'SET_VISIBILITY_FILTER',
            filter: ownProps.filter
          });
        }
      };
    }
    

    从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出Action
    如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样。

    const mapDispatchToProps = {
      onClick: (filter) => {
        type: 'SET_VISIBILITY_FILTER',
        filter: filter
      };
    }
    

    <Provider> 组件

    connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。

    一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。

    React-Redux 提供Provider组件,可以让容器组件拿到state

    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import todoApp from './reducers'
    import App from './components/App'
    
    let store = createStore(todoApp);
    
    render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    )
    

    上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。

    它的原理是React组件的context属性,请看源码。

    class Provider extends Component {
      getChildContext() {
        return {
          store: this.props.store
        };
      }
      render() {
        return this.props.children;
      }
    }
    
    Provider.childContextTypes = {
      store: React.PropTypes.object
    }
    

    上面代码中,store放在了上下文对象context上面。然后,子组件就可以从context拿到store,代码大致如下。

    class VisibleTodoList extends Component {
      componentDidMount() {
        const { store } = this.context;
        this.unsubscribe = store.subscribe(() =>
          this.forceUpdate()
        );
      }
    
      render() {
        const props = this.props;
        const { store } = this.context;
        const state = store.getState();
        // ...
      }
    }
    
    VisibleTodoList.contextTypes = {
      store: React.PropTypes.object
    }
    

    React-Redux自动生成的容器组件的代码,就类似上面这样,从而拿到store

    redux-saga
    • sagaMiddleware中,可以使用takeEvery或者takeLatestAPI来监听某个action
    • 当某个action触发后,saga可以使用callfetchapi发起异步操作,操作完成后使用put函数触发action,同步更新state,从而完成整个State的更新

    0x06 React-router

    • react原生的路由可以满足简单的应用,复杂场景使用起来非常复杂
    • 官方参考文档

    不使用react-router的情况

    import React from 'react'
    import { render } from 'react-dom'
    
    const About = React.createClass({/*...*/})
    const Inbox = React.createClass({/*...*/})
    const Home = React.createClass({/*...*/})
    
    const App = React.createClass({
      getInitialState() {
        return {
          route: window.location.hash.substr(1)
        }
      },
    
      componentDidMount() {
        window.addEventListener('hashchange', () => {
          this.setState({
            route: window.location.hash.substr(1)
          })
        })
      },
    
      render() {
        let Child
        switch (this.state.route) {
          case '/about': Child = About; break;
          case '/inbox': Child = Inbox; break;
          default:      Child = Home;
        }
    
        return (
          <div>
            <h1>App</h1>
            <ul>
              <li><a href="#/about">About</a></li>
              <li><a href="#/inbox">Inbox</a></li>
            </ul>
            <Child/>
          </div>
        )
      }
    })
    
    React.render(<App />, document.body)
    

    上述代码使用this.state.route来动态监控URL HASH部分的变化,从而渲染不同的<Child>组件

    使用react-router的情况

    import React from 'react'
    import { render } from 'react-dom'
    
    // 首先我们需要导入一些组件...
    import { Router, Route, Link } from 'react-router'
    
    // 然后我们从应用中删除一堆代码和
    // 增加一些 <Link> 元素...
    const App = React.createClass({
      render() {
        return (
          <div>
            <h1>App</h1>
            {/* 把 <a> 变成 <Link> */}
            <ul>
              <li><Link to="/about">About</Link></li>
              <li><Link to="/inbox">Inbox</Link></li>
            </ul>
    
            {/*
              接着用 `this.props.children` 替换 `<Child>`
              router 会帮我们找到这个 children
            */}
            {this.props.children}
          </div>
        )
      }
    })
    
    // 最后,我们用一些 <Route> 来渲染 <Router>。
    // 这些就是路由提供的我们想要的东西。
    React.render((
      <Router>
        <Route path="/" component={App}>
          <Route path="about" component={About} />
          <Route path="inbox" component={Inbox} />
        </Route>
      </Router>
    ), document.body)
    

    0xFF 问题总结:

    • 拿不到组件的refs引用?

    组件显示了以后才能拿到组件的refs引用,因为refs拿的是真是DOM虚拟DOM真正插入到HTML DOM的时候才能拿到refs
    显示以后,也不一定可以对内部的参数进行操作,需要delay()或者setTimeout()进行延迟操作


    欢迎关注微信公众号(coder0x00)或扫描下方二维码关注,我们将持续搜寻程序员必备基础技能包提供给大家。


    相关文章

      网友评论

          本文标题:Get Strated of React

          本文链接:https://www.haomeiwen.com/subject/duyhsftx.html