React(四)

作者: osoLife | 来源:发表于2017-07-04 10:27 被阅读0次

    前端组件化

    一个简单的点赞功能

    <!--用火狐浏览器打开-->
    <div class="wrapper">
        <button class="btn">
            <span class="btn-text">点赞</span>
            <span>👍</span>
        </button>
    </div> 
    
    <script>
        const btn=document.querySelector('.btn');
        const btnText=btn.querySelector('.btn-text');
        let isLiked=false;
    
        btn.onclick=()=>{
            isLiked=!isLiked;
            if(isLiked) {
                btnText.innerHTML="取消";
            }else {
                btnText.innerHTML="点赞";
            }
        }
    </script>
    

    结构复用

    <div class="wrapper"></div>
       
    <script>
        class LikeButton {
            render() {
                return ` 
                    <button class="btn">
                        <span class="btn-text">点赞</span>
                        <span>👍</span>
                    </button>
                `
            }
        }
    
        const wrapper=document.querySelector('.wrapper');
        const likeButton1=new LikeButton();
        wrapper.innerHTML=likeButton1.render();
    
        const likeButton2=new LikeButton();
        wrapper.innerHTML+=likeButton2.render();
    </script>
    

    实现简单的组件化

    <div class="wrapper"></div>
       
    <script>
        const createDOMFromString=(domString)=>{
            const div=document.createElement('div');
            div.innerHTML=domString;
            return div;
        }
    
        class LikeButton {
            constructor() {
                this.state={
                    isLiked:false
                };
            }
    
            changeText() {
                const btnText=this.el.querySelector('.btn-text');
                this.state.isLiked=!this.state.isLiked;
                btnText.innerHTML=this.state.isLiked?'取消':'赞成';
            }
    
            render() {
                this.el=createDOMFromString(`
                     <button class="btn">
                        <span class="btn-text">点赞</span>
                        <span>👍</span>
                    </button>
                `);
                this.el.addEventListener('click', this.changeText.bind(this), false);
                return this.el; 
            }         
        }
    
        const wrapper=document.querySelector('.wrapper');
        const likeButton1=new LikeButton();
        wrapper.appendChild(likeButton1.render());
    
        const likeButton2=new LikeButton();
        wrapper.appendChild(likeButton2.render());
    </script>
    

    状态改变->构建新的DOM元素更新页面->重新插入新的DOM元素

    <div class="wrapper"></div>
       
    <script>
         const createDOMFromString=(domString)=>{
             const div=document.createElement('div');
             div.innerHTML=domString;
             return div;
         }
    
         class LikeButton {
             constructor() {
                 this.state={
                     isLiked:false
                 };
             }
    
             setState(state) {
                 const oldEl=this.el;
                 this.state=state;
                 this.el=this.render();
                if (this.onStateChange) this.onStateChange(oldEl, this.el);
             }
    
             changeText() {
                 this.setState({
                     isLiked:!this.state.isLiked
                 });
             }
    
             render() {
                 this.el=createDOMFromString(`
                      <button class="btn">
                         <span class="btn-text">${this.state.isLiked?'取消':'点赞'}</span>
                         <span>👍</span>
                      </button>
                 `);
                 this.el.addEventListener('click', this.changeText.bind(this), false);
                 return this.el; 
             }    
         }
    
         const wrapper=document.querySelector('.wrapper');
         const likeButton1=new LikeButton();
         likeButton1.onStateChange = (oldEl, newEl) => {
            wrapper.insertBefore(newEl, oldEl) // 插入新的元素
            wrapper.removeChild(oldEl) // 删除旧的元素
         }
         wrapper.appendChild(likeButton1.render());
    
         const likeButton2=new LikeButton();
         likeButton2.onStateChange = (oldEl, newEl) => {
               wrapper.insertBefore(newEl, oldEl) // 插入新的元素
               wrapper.removeChild(oldEl) // 删除旧的元素
         }
         wrapper.appendChild(likeButton2.render());
     </script>
    

    搭建开发环境

    // 安装create-react-app工具(一键生成所需要的工程目录,并做好各种配置和依赖)
    // 工具地址:https://github.com/facebookincubator/create-react-app
    npm install -g create-react-app
    
    // 把npm的源改成taobao的源
    npm config set registry https://registry.npm.taobao.org
    
    // 通过create-react-app命令创建一个名为hello-react的工程
    create-react-app hello-react
    
    // 进入工程目录然后通过npm启动工程
    cd hello-react
    npm start
    
    // http://localhost:3000/
    // http://192.168.1.11:3000/
    

    使用JSX描述UI信息

    JSX原理

    要点:
    1.每个DOM元素的结构都可以用JS对象来表示。
    2.编译的过程会把类似HTML的JSX结构转换成JS的对象结构。
    3.JSX是JS语言的一种语法扩展。
    

    组件的render方法

    表达式插入

    要点:
    1.{}内可以放任何JS的代码。
    2.表达式插入不但可以用在标签内部,也可以用在标签的属性上。
    3.如果插入的表达式返回null,那么React会什么都不显示,相当于忽略了该表达式的插入。结合条件返回,可以做到显示或隐藏某些元素。
    

    条件返回

    {}内也可以放置JSX。
    

    JSX元素变量(即JS对象)

    可赋值给变量、作为函数参数传递、作为函数的返回值。
    

    组件的组合、嵌套和组件树

    事件监听

    要点:
    1.React封装了一系列的on*的属性(若没有经过处理,这些on*的事件监听只能用在普通的HTML的标签上,而不能用在组件标签上),地址:https://facebook.github.io/react/docs/events.html#supported-events。
    2.事件属性名都必须要用驼峰命名法。
    

    event对象

    和普通浏览器一样,事件监听函数会被自动传入一个event对象(event对象并不是由浏览器所提供,而是由react内部所创建)。
    

    关于事件中的this

    要点:
    1.React调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClick),而是直接通过函数调用(handleClick),所以事件监听函数内并不能通过this获取到实例。
    2.如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法bind到当前实例上再传入给React的onClick事件监听,也可以在bind的时候给事件监听函数传入一些参数。
    

    组件的state和setState

    state

    一个组件的显示形态是可以由它数据状态和配置参数决定的。
    

    setState接收对象参数

    要点:
    1.setState方法由父类所提供。当我们调用这个函数的时候,React会更新组件的状态state,并且重新调用render方法,然后再把render方法所渲染的最新的内容显示到页面上。
    2.接收一个对象或者函数作为参数。
    3.传入一个对象的时候,这个对象表示该组件的新状态。但你只需要传入需要给更新的部分就可以了,而不需要传入整个对象。
    

    setState接收函数参数

    当你调用setState的时候,React并不会马上修改state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到state当中,然后再触发组件更新。
    

    setState合并

    React内部会把JS事件循环中的消息队列的同一个消息中的setState都进行合并以后再重新渲染组件,因此不用担心多次进行setState会带来性能问题。
    

    配置组件的props

    要点:
    1.每个组件都接收一个props参数,它是一个对象,包含了所有你对这个组件的配置。
    2.组件内部通过this.props的方式获取到组件的参数。
    3.在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为props对象的键值。
    4.可以把任何类型的数据作为组件的参数,包括字符串、数字、对象、数组、甚至是函数等等。
    

    默认配置defaultProps

    里面是对props中各个属性的默认配置。
    

    props不可变

    props一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的props,从而达到更新的效果。
    

    state vs props

    要点:
    1.state的主要作用是用于组件保存、控制、修改自己的可变状态。(state是让组件控制自己的状态)
    2.props的主要作用是让使用该组件的父组件可以传入参数来配置该组件。(props是让外部对组件自己进行配置)
    3.尽量少用state,尽量多用props。
    4.没有state的组件叫无状态组件,设置了state的叫做有状态组件。
    

    渲染列表数据

    渲染存放JSX元素的数组

    <div id="box"></div>
    <!--如果往{}中放入一个数组,React会帮你把数组里面的元素一个个的罗列并渲染出来-->    
    <script type="text/babel">
        class Index extends React.Component {
            render() {
                return (
                    <div>
                        {
                            [
                                <span>i </span>,
                                <span>love </span>,
                                <span>react</span>
                            ]
                        }
                    </div>
                )
            }
        }
    
        ReactDOM.render(
            <Index />,
            document.getElementById('box')
        )
    </script>
    

    使用map渲染列表数据

     <div id="box"></div>
        
     <script type="text/babel">
         const users=[
            {
                username:'osoLife',
                age:18,
                sex:'male'
            },
            {
                username:'michael',
                age:28,
                sex:'female'
            },
            {
                username:'moli',
                age:38,
                sex:'male'
            }
        ]
    
        class Index extends React.Component {         
            render() {
                const arr=[];
                // 循环每个用户,构建JSX,push到数组中
                for(let user of users) {
                    arr.push(
                        <div>
                            <div>姓名:{user.username}</div>
                            <div>年龄:{user.age}</div>
                            <div>性别:{user.sex}</div>
                            <hr/>
                        </div>
                    );
                }
    
                return (
                   <div>{arr}</div>
                )
            }
        }
    
        ReactDOM.render(
            <Index />,
            document.getElementById('box')
        )
    </script>
    
    <div id="box"></div>
    
    <script type="text/babel">
        const users=[
            {
                username:'osoLife',
                age:18,
                sex:'male'
            },
            {
                username:'michael',
                age:28,
                sex:'female'
            },
            {
                username:'moli',
                age:38,
                sex:'male'
            }
        ]
    
        class Index extends React.Component {         
            render() {
                return (
                    <div>
                       {
                            users.map(
                                (user)=>{
                                    return (
                                        <div>
                                            <div>姓名:{user.username}</div>
                                            <div>年龄:{user.age}</div>
                                            <div>性别:{user.sex}</div>
                                            <hr/>
                                        </div>
                                    )
                                }
                            )
                       }
                    </div>
                )
            }
        }
    
        ReactDOM.render(
            <Index />,
            document.getElementById('box')
        )
    </script>
    
    <div id="box"></div>
    
    <script type="text/babel">
        const users=[
            {
                username:'osoLife',
                age:18,
                sex:'male'
            },
            {
                username:'michael',
                age:28,
                sex:'female'
            },
            {
                username:'moli',
                age:38,
                sex:'male'
            }
        ]
    
        class User extends React.Component {
            render() {
                const {user}=this.props;
                return (
                    <div>
                        <div>姓名:{user.username}</div>
                        <div>年龄:{user.age}</div>
                        <div>性别:{user.sex}</div>
                        <hr/>
                    </div>
                );
            }
        }
    
        class Index extends React.Component {         
            render() {
                return (
                    <div>
                        {
                            users.map(
                                (user)=><User user={user} />
                            )
                        }
                    </div>
                );
            }
        }
    
        ReactDOM.render(
            <Index />,
            document.getElementById('box')
        )
    </script>
    
    <!--对于用表达式套数组罗列到页面上的元素,都要为每个元素加上key属性,这个key必须是每个元素唯一的标识-->
    

    实战分析:评论功能(一)

    组件划分

    React中一切都是组件,用React构建的功能其实就是由各种组件组合而成。
    

    组件实现

    // 构建一个新的工程目录
    create-react-app comment-app
    
    // 在工程目录下的src/目录下新建四个文件
    CommentApp.js
    CommentInput.js
    CommentList.js
    Comment.js
    
    // CommentApp.js
    import React,{ Component } from 'react';
    import CommentInput from './CommentInput';
    import CommentList from './CommentList';
    
    class CommentApp extends React.Component {
        render() {
            return (
                <div>
                    <CommentInput />
                    <CommentList />
                </div>
            )
        }
    }
    
    export default CommentApp
    
    // CommentInput.js
    import React,{ Component } from 'react';
    
    class CommentInput extends React.Component {
        render() {
            return (
                <div>CommentInput</div>
            )
        }
    }
    
    export default CommentInput
    
    // CommentList.js
    import React,{ Component } from 'react';
    
    class CommentList extends React.Component {
        render() {
            return (
                <div>CommentList</div>
            )
        }
    }
    
    export default CommentList
    
    // 修改src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import CommentApp from './CommentApp';
    import './index.css';
    
    ReactDOM.render(
        <CommentApp />,
        document.getElementById('root')    
    )
    
    // 进入工程目录启动工程
    npm run start
    

    添加样式

    // 修改CommentApp中的render方法,给它添加一个wrapper类名
    import React,{ Component } from 'react';
    import CommentInput from './CommentInput';
    import CommentList from './CommentList';
    
    class CommentApp extends React.Component {
        render() {
            return (
                <div className="wrapper">
                    <CommentInput />
                    <CommentList />
                </div>
            )
        }
    }
    
    export default CommentApp
    
    // index.css
    body {
      margin: 0;
      padding: 0;
      font-family: sans-serif;
      background-color: #fbfbfb;
    }
    .wrapper {
      width: 500px;
      margin: 10px auto;
      font-size: 14px;
      background-color: #fff;
      border: 1px solid #f1f1f1;
      padding: 20px;
    }
    

    实战分析:评论功能(二)

    处理用户输入

    // 修改ComponentInput.js
    import React,{ Component } from 'react';
    
    class CommentInput extends React.Component {
        render() {
            return (
                <div className="comment-input">
                    <div className="comment-field">
                        <span className="comment-field-name">用户名:</span>
                        <div className="comment-field-input">
                            <input />
                        </div>
                    </div>
                    <div className="comment-field">
                        <span className="comment-field-name">评论内容:</span>
                        <div className="comment-field-input">
                            <textarea />
                        </div>
                    </div>
                    <div className="comment-field-button">
                        <button>
                            发布
                        </button>
                    </div>
                </div>
            )
        }
    }
    
    export default CommentInput
    
    // 修改index.css
    body {
      margin: 0;
      padding: 0;
      font-family: sans-serif;
      background-color: #fbfbfb;
    }
    
    .wrapper {
      width: 500px;
      margin: 10px auto;
      font-size: 14px;
      background-color: #fff;
      border: 1px solid #f1f1f1;
      padding: 20px;
    }
    
    /* 评论框样式 */
    .comment-input {
      background-color: #fff;
      border: 1px solid #f1f1f1;
      padding: 20px;
      margin-bottom: 10px;
    }
    
    .comment-field {
      margin-bottom: 15px;
      display: flex;
    }
    
    .comment-field .comment-field-name {
      display: flex;
      flex-basis: 100px;
      font-size: 14px;
    }
    
    .comment-field .comment-field-input {
      display: flex;
      flex: 1;
    }
    
    .comment-field-input input,
    .comment-field-input textarea {
      border: 1px solid #e6e6e6;
      border-radius: 3px;
      padding: 5px;
      outline: none;
      font-size: 14px;
      resize: none;
      flex: 1;
    }
    
    .comment-field-input textarea {
      height: 100px;
    }
    
    .comment-field-button {
      display: flex;
      justify-content: flex-end;
    }
    
    .comment-field-button button {
      padding: 5px 10px;
      width: 80px;
      border: none;
      border-radius: 3px;
      background-color: #00a3cf;
      color: #fff;
      outline: none;
      cursor: pointer;
    }
    
    .comment-field-button button:active {
      background: #13c1f1;
    }
    
    /* 评论列表样式 */
    .comment-list {
      background-color: #fff;
      border: 1px solid #f1f1f1;
      padding: 20px;
    }
    
    /* 评论组件样式 */
    .comment {
      display: flex;
      border-bottom: 1px solid #f1f1f1;
      margin-bottom: 10px;
      padding-bottom: 10px;
      min-height: 50px;
    }
    
    .comment .comment-user {
      flex-shrink: 0;
    }
    
    .comment span {
      color: #00a3cf;
      font-style: italic;
    }
    
    .comment p {
      margin: 0;
      /*text-indent: 2em;*/
    }
    
    // 加入处理逻辑
    import React,{ Component } from 'react'
    
    class CommentInput extends Component {
        constructor() {
            super();
            this.state={
                username:'',
                content:''
            };
        }
    
        handleUsernameChange(event) {
            this.setState({
                username:event.target.value
            });
        }
    
        handleContentChange(event) {
            this.setState({
                content:event.target.value
            });
        }
    
        handleSubmit() {
            if(this.props.onSubmit) {
                const {username,content}=this.state;
                this.props.onSubmit({username,content});
            };
            this.setState({content:''});
        }
    
        render() {
            return (
                <div className="comment-input">
                    <div className="comment-field">
                        <span className="comment-field-name">用户名:</span>
                        <div className="comment-field-input">
                            <input value={this.state.username} onChange={this.handleUsernameChange.bind(this)} />
                        </div>
                    </div>
                    <div className="comment-field">
                        <span className="comment-field-name">评论内容:</span>
                        <div className="comment-field-input">
                            <textarea value={this.state.content} onChange={this.handleContentChange.bind(this)} />
                        </div>
                    </div>
                    <div className="comment-field-button">
                        <button onClick={this.handleSubmit.bind(this)}>
                            发布
                        </button>
                    </div>
                </div>
            )
        }
    }
    
    export default CommentInput
    
    // 要点:
    1.<input />、<textarea />、<select />这样的输入控件被设置了value值,那么它们的值永远以被设置的值为准,值不变,value就不会变化。
    2.类似于<input />、<select />、<textarea>这些元素的value值被React所控制、渲染的组件,在React当中被称为受控组件。
    

    向父组件传递数据

    // 修改CommentApp.js
    import React,{ Component } from 'react'
    import CommentInput from './CommentInput'
    import CommentList from './CommentList'
    
    class CommentApp extends Component {
        handleSubmitComment(comment) {
            console.log(comment);
        }
    
        render() {
            return (
                <div className="wrapper">
                    <CommentInput onSubmit={this.handleSubmitComment.bind(this)} />
                    <CommentList />
                </div>
            );
        }
    }
    
    export default CommentApp
    

    实战分析:评论功能(三)

    // 修改CommentList.js
    import React,{ Component } from 'react'
    
    class CommentList extends Component {
        render() {
            const comments=[
                {
                    username:'osoLife',
                    content:'i'
                },
                {
                    username:'moli',
                    content:'love'
                },
                {
                    username:'michael',
                    content:'react'
                }
            ];
    
            return (
                <div>
                    {
                        comments.map(
                            (comment,i)=>{
                                return (
                                    <div key={i}>
                                        {comment.username}:{comment.content}
                                    </div>
                                )
                            }
                        )
                    }
                </div>
            );
        }
    }
    
    export default CommentList
    
    // 修改Comment.js
    import React,{ Component } from 'react';
    
    class Comment extends Component {
        render() {
            return (
                <div className="comment">
                    <div className="comment-user">
                        <span>{this.props.comment.username} </span>:
                    </div>
                    <p>{this.props.comment.content}</p>
                </div>
            )
        }
    }
    
    export default Comment
    
    // 修改CommentList.js
    import React,{ Component } from 'react';
    import Comment from './Comment';
    
    class CommentList extends Component {
        // 防止comments不传入的情况
        static defaultProps={
            comments:[]
        }
        
        // 删除测试数据,改成props获取评论数据
        render() {
            return (
                <div>
                    {
                        this.props.comments.map(
                            (comment,i)=><Comment comment={comment} key={i} />
                        )
                    }
                </div>
            );
        }
    }
    
    export default CommentList
    
    // 修改CommentApp.js
    import React,{ Component } from 'react'
    import CommentInput from './CommentInput'
    import CommentList from './CommentList'
    
    class CommentApp extends Component {
        constructor() {
            super();
            this.state={
                comments:[]
            }
        }
    
        handleSubmitComment(comment) {
            this.state.comments.push(comment);
            this.setState({
                comments:this.state.comments
            })
        }
    
        render() {
            return (
                <div className="wrapper">
                    <CommentInput onSubmit={this.handleSubmitComment.bind(this)} />
                    <CommentList comments={this.state.comments} />
                </div>
            );
        }
    }
    
    export default CommentApp
    
    // 给handleSubmitComment加入简单的数据检查
    handleSubmitComment(comment) {
        if(!comment) {
            return;
        }
    
        if(!comment.username) {
            return alert('请输入用户名');
        }
    
        if(!comment.content) {
            return alert('请输入评论内容');
        }
    
        this.state.comments.push(comment);
        this.setState({
            comments:this.state.comments
        });
    }
    

    前端应用状态管理——状态提升

    当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共组件中去管理,用props传递数据或者函数来管理这种依赖或者影响的行为。
    

    挂载阶段的组件生命周期(一)

    React将组件渲染,并且构造DOM元素然后塞入页面的过程称为组件的挂载。
    
    // 一个组件的方法调用过程
    -> constructor()
    -> render()
    // 然后构造DOM元素插入页面
    
    -> constructor()
    -> componentWillMount()
    -> render()
    // 然后构造DOM元素插入页面
    -> componentDidMount()
    // ...
    // 即将从页面中删除
    -> componentWillUnmount()
    // 从页面中删除
    
    <div id="box"></div>
    
    <script type="text/babel">
        class Header extends React.Component {
            constructor() {
                super();
                console.log('constructor');
            }
    
            componentWillMount() {
                console.log('component will mount');
            }
    
            componentDidMount() {
                console.log('component did mount');
            }
    
            render() {
                console.log('render');
                return (
                    <div>
                        <h1 className="title">osoLife</h1>
                    </div>
                );
            }          
        }
    
        ReactDOM.render(
            <Header />,
            document.getElementById("box")
        )
    </script>
    
    <div id="box"></div>
    
    <script type="text/babel">
        class Header extends React.Component {
            constructor() {
                super();
                console.log('constructor');
            }
    
            componentWillMount() {
                console.log('component will mount');
            }
    
            componentDidMount() {
                console.log('component did mount');
            }
    
            componentWillUnmount() {
                console.log('component will unmount');
            }
    
            render() {
                console.log('render');
                return (
                    <div>
                        <h1 className="title">osoLife</h1>
                    </div>
                )
            }          
        }
    
        class Index extends React.Component {
            constructor() {
                super();
                this.state={
                    isShowHeader:true
                };
            }
    
            handleShowOrHide() {
                this.setState({
                    isShowHeader:!this.state.isShowHeader
                }); 
            }
    
            render() {
                return (
                    <div>
                        {this.state.isShowHeader ? <Header />:null}
                        <button onClick={this.handleShowOrHide.bind(this)}>
                            显示或者隐藏标题
                        </button>
                    </div>
                )
            }
        }
    
        ReactDOM.render(
            <Index />,
            document.getElementById('box')
        )
    </script>
    
    <!--React控制了组件的删除过程,在组件删除之前调用组件定义的componentWillUnmount-->
    

    挂载阶段的组件生命周期(二)

    <div id="box"></div>
    
    <script type="text/babel">
        class Clock extends React.Component {
            constructor() {
                super();
                this.state={
                    date:new Date()
                };
            }
    
            componentWillMount() {
               this.timer=setInterval(()=>{
                   this.setState({
                       date:new Date()
                   });
               },1000)
            }
    
            render() {
                return (
                    <div>
                       <h1>
                            <p>现在的时间是</p>
                            {this.state.date.toLocaleTimeString()}
                       </h1>
                    </div>
                );
            }
        }
    
        class Index extends React.Component {
            render() {
                return (
                    <div>
                        <Clock />
                    </div>
                );
            }
        }
    
        ReactDOM.render(
            <Index />,
            document.getElementById('box')
        )
    </script>
    
    <div id="box"></div>
    
    <script type="text/babel">
        class Clock extends React.Component {
            constructor() {
                super();
                this.state={
                    date:new Date()
                };
            }
    
            // 在componentWillMount进行组件的启动工作,例如Ajax数据拉取、定时器的启动
            componentWillMount() {
               this.timer=setInterval(()=>{
                   this.setState({
                       date:new Date()
                   });
               },1000);
            }
    
            // 组件从页面上销毁的时候,需要一些数据的清理,例如定时器的清理
            componentWillUnmount() {
                clearInterval(this.timer);
            }
    
            render() {
                return (
                    <div>
                       <h1>
                            <p>现在的时间是</p>
                            {this.state.date.toLocaleTimeString()}
                       </h1>
                    </div>
                );
            }
        }
    
        class Index extends React.Component {
            constructor() {
                super();
                this.state={
                    isShowClock:true
                };
            }
    
            handleShowOrHide() {
                this.setState({
                    isShowClock:!this.state.isShowClock
                });
            }
    
            render() {
                return (
                    <div>
                       {this.state.isShowClock ? <Clock />:null}
                       <button onClick={this.handleShowOrHide.bind(this)}>
                            显示或隐藏时钟
                       </button>
                    </div>
                );
            }
        }
    
        ReactDOM.render(
            <Index />,
            document.getElementById('box')
        )
    </script>
    

    更新阶段的组件生命周期

    // 组件的挂载指的是将组件渲染并且构造DOM元素然后插入页面的过程,这是一个从无到有的过程。React提供一些生命周期函数可以给我们在这个过程中做一些操作。除了挂载阶段,还有一种“更新阶段”。
    
    // 更新阶段的组件生命周期:
    1.shouldComponentUpdate(nextProps,nextState):通过这个方法可以控制组件是否重新渲染,如果返回false组件就不会重新渲染。
    2.componentWillReceiveProps(nextProps):组件从父组件接收到新的props之前调用。
    3.componentWillUpdate():组件开始重新渲染之前调用。
    4.componentDidUpdate():组件重新渲染并且把更改变更到真实的DOM以后调用。
    

    ref和React中的DOM操作

    // 通过ref属性来获取已经挂载的元素的DOM节点
    // 原则:能不用ref就不用
    <div id="box"></div>
    
    <script type="text/babel">
        class AutoFocusInput extends React.Component {
            componentDidMount() {
                this.input.focus();
            }
    
            render() {
                return (
                    <input ref={(input)=>this.input=input} />
                );
            }
        }
    
        ReactDOM.render(
            <AutoFocusInput />,
            document.getElementById("box")
        )
    </script>
    
    <!--可以给组件标签加上ref,这样获取到的是组件在React内部初始化的实例-->
    

    props.children和容器类组件

    <!--组件本身是一个不带任何内容的方形容器,我们可以在用这个组件的时候给它传入任意内容 -->
    <!--通过给Card组件传入一个content属性,这个属性可以传入任意的JSX结构,然后在Card内部会通过{this.props.content}把内容渲染到页面上-->
    <div id="box"></div>
    
    <script type="text/babel">
        class Card extends React.Component {
            render() {
                return (
                    <div className="card">
                        <div className="card-content">
                            {this.props.content}
                        </div>
                    </div>
                )
            }
        }
    
        ReactDOM.render(
            <Card content={
                <div>
                    <h2>osoLife</h2>
                    <div>一个前端工程师</div>
                    订阅我:<input />
                </div>
            } />,
            document.getElementById("box")
        )
    </script>
    
    <!--问题:如果Card除了content以外还能传入其它属性的话,那么这些JSX和其它属性就会混在一起,不好维护-->
    
    // React会把嵌套的JSX元素一个个都放到数组当中,然后通过props.children传给了Card。
    // 由于JSX会把插入表达式里面数组中的JSX一个个罗列下来显示。
    // 所有嵌套在组件中的JSX结构都可以在组件内部通过props.children获取到。
    <div id="box"></div>
    
    <script type="text/babel">
        class Card extends React.Component {
            render() {
                return (
                    <div className="card">
                        <div className="card-content">
                            {this.props.children}
                        </div>
                    </div>
                )
            }
        }  
    
        ReactDOM.render(
            <Card>
                <h2>osoLife</h2>
                <div>一个前端工程师</div>
                订阅我:<input />
            </Card>,
            document.getElementById("box")  
        )      
    </script>
    
    // 在组件内部把数组中的JSX元素安置在不同的地方示例:
    <div id="box"></div>
    
    <script type="text/babel">
        class Layout extends React.Component {
            render() {
                return (
                    <div className="two-cols-layout">
                        <div className="sidebar">
                            {this.props.children[0]}
                        </div>
                        <div className="main">
                            {this.props.children[1]}
                        </div>
                    </div>
                )
            }
        }
    </script>
    

    dangerouslySetHTML和style属性

    dangerouslySetHTML

    // 出于安全考虑的原因(XSS攻击),在React当中所有的表达式插入的内容都会被自动转义。
    <div id="box"></div>
    
    <script type="text/babel">
        class Editor extends React.Component {
            constructor() {
                super();
                this.state={
                    content:'<h1>osoLife</h1>'
                }
            }
    
            render() {
                return (
                    <div className="editor-wrapper">
                        {this.state.content}
                    </div>
                )
            }
        }
    </script>
    
    // 通过dangerouslySetInnerHTML属性动态设置元素的innerHTML(给dangerouslySetInnerHTML传入一个对象,这个对象的__html属性值就相当于元素的innerHTML)
    <div id="box"></div>
    
    <script type="text/babel">
        class Editor extends React.Component {
            constructor() {
                super();
                this.state={
                    content:'<h1>osoLife</h1>'
                }
            }
    
            render() {
                return (
                    <div className="editor-wrapper"
                        dangerouslySetInnerHTML={{
                        __html:this.state.content
                    }} />
                )
            }
        }
    
        ReactDOM.render(
            <Editor />,
            document.getElementById("box")
        )
    </script>
    

    style

    // 需要把CSS属性变成一个对象再传给元素(原来CSS属性中带“-”的元素都必须去掉“-”换成驼峰命名)
    <h1 style={{fontSize:'12px',color:'red'}}>osoLife</h1>
    

    PropTypes和组件参数验证

    // 安装第三方库:prop-types
    npm install --save prop-types
    
    // 给组件的配置参数加上类型验证
    import React,{ Component } from 'react';
    import PropTypes from 'prop-types';
    
    class Comment extends Component {
        static propTypes={
            comment:PropTypes.object
        }
    
        render() {
            const { comment }=this.props;
            return (
                <div className="comment">
                    <div className="comment-user">
                        <span>{comment.username} </span> :
                    </div>
                    <p>{comment.content}</p>
                </div>
            )
        }
    }
    
    export default Comment
    // 可选参数我们可以通过配置defaultProps,让它在不传入的时候有默认值。
    
    import React,{ Component } from 'react';
    import PropTypes from 'prop-types';
    
    class Comment extends Component {
        // 通过isRequired关键字来强制组件某个参数必须传入
        static propTypes={
            comment:PropTypes.object.isRequired
        }
    
        render() {
            const { comment }=this.props;
            return (
                <div className="comment">
                    <div className="comment-user">
                        <span>{comment.username} </span> :
                    </div>
                    <p>{comment.content}</p>
                </div>
            )
        }
    }
    
    export default Comment
    
    // PropTypes提供了一系列的数据类型可以用来配置组件的参数
    PropTypes.array
    PropTypes.bool
    PropTypes.func
    PropTypes.number
    PropTypes.object
    PropTypes.string
    PropTypes.node
    PropTypes.element
    ...
    

    实战分析:评论功能(四)

    // index.css
    body {
      margin: 0;
      padding: 0;
      font-family: sans-serif;
      background-color: #fbfbfb;
    }
    
    .wrapper {
      width: 500px;
      margin: 10px auto;
      font-size: 14px;
      background-color: #fff;
      border: 1px solid #f1f1f1;
      padding: 20px;
    }
    
    /* 评论框样式 */
    .comment-input {
      background-color: #fff;
      border: 1px solid #f1f1f1;
      padding: 20px;
      margin-bottom: 10px;
    }
    
    .comment-field {
      margin-bottom: 15px;
      display: flex;
    }
    
    .comment-field .comment-field-name {
      display: flex;
      flex-basis: 100px;
      font-size: 14px;
    }
    
    .comment-field .comment-field-input {
      display: flex;
      flex: 1;
    }
    
    .comment-field-input input,
    .comment-field-input textarea {
      border: 1px solid #e6e6e6;
      border-radius: 3px;
      padding: 5px;
      outline: none;
      font-size: 14px;
      resize: none;
      flex: 1;
    }
    
    .comment-field-input textarea {
      height: 100px;
    }
    
    .comment-field-button {
      display: flex;
      justify-content: flex-end;
    }
    
    .comment-field-button button {
      padding: 5px 10px;
      width: 80px;
      border: none;
      border-radius: 3px;
      background-color: #00a3cf;
      color: #fff;
      outline: none;
      cursor: pointer;
    }
    
    .comment-field-button button:active {
      background: #13c1f1;
    }
    
    /* 评论列表样式 */
    .comment-list {
      background-color: #fff;
      border: 1px solid #f1f1f1;
      padding: 20px;
    }
    
    /* 评论组件样式 */
    .comment {
      position: relative;
      display: flex;
      border-bottom: 1px solid #f1f1f1;
      margin-bottom: 10px;
      padding-bottom: 10px;
      min-height: 50px;
    }
    
    .comment .comment-user {
      flex-shrink: 0;
    }
    
    .comment-username {
      color: #00a3cf;
      font-style: italic;
    }
    
    .comment-createdtime {
      padding-right: 5px;
      position: absolute;
      bottom: 0;
      right: 0;
      padding: 5px;
      font-size: 12px;
    }
    
    .comment:hover .comment-delete {
      color: #00a3cf;
    }
    
    .comment-delete {
      position: absolute;
      right: 0;
      top: 0;
      color: transparent;
      font-size: 12px;
      cursor: pointer;
    }
    
    .comment p {
      margin: 0;
      /*text-indent: 2em;*/
    }
    
    code {
      border: 1px solid #ccc;
      background: #f9f9f9;
      padding: 0px 2px;
    }
    

    自动聚焦到评论框

    // 修改CommentInput.js
    // 给输入框元素加上ref以便获取到DOM元素
    ...
    <textarea ref={(textarea)=>this.textarea=textarea} value={this.state.content} onChange={this.handleContentChange.bind(this)} />
    ...
    
    // 给CommentInput组件加上ComponentDidMount生命周期
    static propTypes={
        onSubmit:PropTypes.func
    }
    
    componentDidMount() {
        this.textarea.focus()
    }
    

    持久化用户名

    // 监听用户名输入框失去焦点事件onBlur
    <input value={this.state.username} onBlur={this.handleUsernameBlur.bind(this)} onChange={this.handleUsernameChange.bind(this)} />
    
    // 在handleUsernameBlur中把用户的输入内容保存到LocalStorage当中
    // 在handleUsernameBlur中把用户输入的内容传给_saveUsername私有方法(所有私有方法都以_开头)。
    _saveUsername(username) {
        localStorage.setItem('username',username)
    }
    
    handleUsernameBlur(event) {
        this._saveUsername(event.target.value)
    }
    
    handleUsernameChange(event) {
        this.setState({
            username:event.target.value
        });
    }
    
    componentWillMount() {
        this._loadUsername()
    }
    
    _loadUsername() {
        const username=localStorage.getItem('username')
        if(username) {
            this.setState({
                username
            })
        }
    }
    
    // 规范:
    组件的私有方法都用“_”开头,所有事件监听的方法都用handle开头,把事件监听方法传给组件的时候,属性名用on开头。
    
    // 组件的内容编写顺序:
    1.static开头的类属性,如defaultProps、propTypes。
    2.构造函数,constructor。
    3.getter/setter。
    4.组件生命周期。
    5._开头的私有方法。
    6.事件监听方法,handle*。
    7.render*开头的方法,有时候render()方法里面的内容会分开到不同函数里面进行,这些函数都以render*开头。
    8.render()方法。
    

    完整代码

    // CommentApp.js
    import React, { Component } from 'react';
    import CommentInput from './CommentInput';
    import CommentList from './CommentList';
    
    class CommentApp extends Component {
      constructor() {
        super();
        this.state = {
          comments: []
        };
      }
    
      handleSubmitComment(comment) {
        if(!comment) {
            return;
        } 
        if (!comment.username) {
            return alert('请输入用户名');
        } 
        if (!comment.content) {
            return alert('请输入评论内容');
        } 
        this.state.comments.push(comment);
        this.setState({
          comments: this.state.comments
        });
      }
    
      render() {
        return (
          <div className='wrapper'>
            <CommentInput onSubmit={this.handleSubmitComment.bind(this)} />
            <CommentList comments={this.state.comments} />
          </div>
        )
      }
    }
    
    export default CommentApp
    
    // CommentInput.js
    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    
    class CommentInput extends Component {
      static propTypes={
        onSubmit:PropTypes.func
      }
    
      constructor() {
        super();
        this.state={
          username: '',
          content: ''
        };
      }
      
      componentWillMount() {
          this._loadUsername();
      }
    
      componentDidMount() {
          this.textarea.focus();
      }
    
      _saveUsername(username) {
          localStorage.setItem('username',username);
      }
    
      _loadUsername() {
          const username=localStorage.getItem('username');
          if(username) {
              this.setState({
                  username
              })
          };
      }
    
      handleUsernameChange(event) {
        this.setState({
          username: event.target.value
        })
      }
    
      handleUsernameBlur(event) {
          this._saveUsername(event.target.value)
      }
    
      handleContentChange(event) {
        this.setState({
          content: event.target.value
        })
      }
    
      handleSubmit() {
        if (this.props.onSubmit) {
          this.props.onSubmit({
            username: this.state.username,
            content: this.state.content,
          })
        }
        this.setState({ content: '' })
      }
    
      render() {
        return (
          <div className='comment-input'>
            <div className='comment-field'>
              <span className='comment-field-name'>用户名:</span>
              <div className='comment-field-input'>
                <input
                  value={this.state.username}
                  onBlur={this.handleUsernameBlur.bind(this)}
                  onChange={this.handleUsernameChange.bind(this)} />
              </div>
            </div>
            <div className='comment-field'>
              <span className='comment-field-name'>评论内容:</span>
              <div className='comment-field-input'>
                <textarea
                  ref={(textarea)=>this.textarea=textarea}
                  value={this.state.content}
                  onChange={this.handleContentChange.bind(this)} />
              </div>
            </div>
            <div className='comment-field-button'>
              <button
                onClick={this.handleSubmit.bind(this)}>
                发布
              </button>
            </div>
          </div>
        )
      }
    }
    
    export default CommentInput
    
    // CommentList.js
    import React, { Component } from 'react';
    import Comment from './Comment';
    
    class CommentList extends Component {
      static defaultProps={
        comments: []
      }
    
      render() {
        return (
          <div>
            {this.props.comments.map((comment, i) =>
              <Comment comment={comment} key={i} />
            )}
          </div>
        )
      }
    }
    
    export default CommentList
    
    // Comment.js
    import React,{ Component } from 'react';
    
    class Comment extends Component {
      render() {
        return (
          <div className='comment'>
            <div className='comment-user'>
              <span>{this.props.comment.username} </span>:
            </div>
            <p>{this.props.comment.content}</p>
          </div>
        )
      }
    }
    
    export default Comment
    

    实战分析:评论功能(五)

    持久化评论

    
    

    高阶组件

    // 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件
    // 高阶组件的作用是用于代码复用,可以把组件之间可复用的代码、逻辑抽离到高阶组件当中。新的组件和传入的组件通过props传递信息
    const NewComponent=higherOrderComponent(OldComponent)
    
    // 一个简单的高阶组件
    import React,{ Component } from 'react';
    
    export default (WrappendComponent)=>{
        class NewComponent extends Component {
            render() {
                return <WrappendComponent />
            }
        }
        return NewComponent;
    }
    
    // 给NewComponent做一些数据启动工作
    import React,{ Component } from 'react';
    
    export default (WrappendComponent,name)=>{
        class NewComponent extends Component {
            constructor() {
                super();
                this.state={
                    data:null
                }
            }
    
            componentWillMount() {
                let data=localStorage.getItem(name);
                this.setState({
                    data
                })
            }
    
            render() {
                return <WrappendComponent data={this.state.data} />
            }
        }
        return NewComponent;
    }
    <!--现在NewComponent会根据第二个参数name在挂载阶段从LocalStorage加载数据-->
    
    // 假设上面的代码是在src/wrapWithLoadData.js文件中,我们在别的地方这么用它
    import wrapWithLoadData from './wrapWithLoadData';
    
    class InputWithUserName extends Component {
        render() {
            return <input value={this.props.data} />
        }
    }
    
    InputWithUserName=wrapWidthLoadData(InputWithUserName,'username')
    export default InputWithUserName
    
    // 别人用这个组件的时候实际是用了被加工过的组件
    import InputWithUserName from './InputWithUserName';
    
    class Index extends Component {
        render() {
            return (
                <div>
                    用户名:<InputWithUserName />
                </div>
            )
        }
    }
    

    context

    // 一个组件可以通过getChildContext方法返回一个对象,这个对象就是子树的context,提供context的组件必须提供childContextTypes作为context的声明和验证。
    // 一个组件的context只有它的子组件能够访问,它的父组件是不能访问的。
    // 就如全局变量一样,context里面的数据能被随意接触就能被随意修改。
    
    import React, { Component } from 'react';
    import ReactDOM from 'react-dom';
    import PropTypes from 'prop-types';
    
    class Index extends React.Component {
      static childContextTypes={
        themeColor:PropTypes.string
      }
    
      constructor() {
        super();
        this.state={
          themeColor:'red'
        };
      }
    
      getChildContext() {
        return {
          themeColor:this.state.themeColor
        };
      }
    
      render() {
        return (
          <div>
            <Header />
            <Main />
          </div>
        );
      }
    }
    
    class Header extends React.Component {
      render() {
        return (
          <div>
            <h2>This is header</h2>
            <Title />
          </div>
        )
      }
    }
    
    class Main extends React.Component {
      render() {
        return (
          <div>
            <h2>This is main</h2>
            <Content />
          </div>
        )
      }
    }
    
    class Title extends React.Component {
      static contextTypes={
        themeColor: PropTypes.string
      }
    
      render() {
        return (
          <h1 style={{color:this.context.themeColor}}>这里的标题是红色</h1>
        )
      }
    }
    
    class Content extends React.Component {
      render() {
        return (
          <div>
            <h2>这里是内容</h2>
          </div>
        )
      }
    }
    
    ReactDOM.render(
      <Index />,
      document.getElementById('root')
    )
    
    export default Index
    

    Redux(一):优雅地修改共享状态

    // 用create-react-app新建一个项目make-redux
    // 修改public/index.html
    <body>
        <div id="title"></div>
        <div id="content"></div>
    </body>
    
    // 删除src/index.js,添加下面的代码
    // 应用的状态
    const appState={
        title:{
            text:'标题',
            color:'red'
        },
        content:{
            text:'内容',
            color:'blue'
        }
    }
    
    // 通过渲染函数把上面状态的数据渲染到页面上
    function renderApp(appState) {
        renderTitle(appState.title);
        renderContent(appState.content);
    }
    
    function renderTitle(title) {
        const titleDOM=document.getElementById('title');
        titleDOM.innerHTML=title.text;
        titleDOM.style.color=title.color;
    }
    
    function renderContent(content) {
        const contentDOM=document.getElementById('content');
        contentDOM.innerHTML=content.text;
        contentDOM.style.color=content.color;
    }
    // 调用
    renderApp(appState);
    
    // 问题:
    1.所有对共享状态的操作都是不可预料的。
    2.“模块(组件)之间需要共享数据”,和“数据可能被任意修改导致不可预料的结果”之间的矛盾。
    
    // 解决方法:
    let appState={
        title:{
            text:'标题',
            color:'red'
        },
        content:{
            text:'内容',
            color:'blue'
        }
    }
    
    // 专门负责数据的修改
    // 所有对数据的操作必须通过dispatch函数
    // 参数action是一个对象,type字段来声明到底想干什么
    function dispatch(action) {
        switch(action.type) {
            case 'UPDATE_TITLE_TEXT':
                appState.title.text=action.text;
                break;
            case 'UPDATE_TITLE_COLOR':
                appState.title.color=action.color;
                break;
            default:
                break;
        }   
    }
    
    function renderApp(appState) {
        renderTitle(appState.title);
        renderContent(appState.content);
    }
    
    function renderTitle(title) {
        const titleDOM=document.getElementById('title');
        titleDOM.innerHTML=title.text;
        titleDOM.style.color=title.color;
    }
    
    function renderContent(content) {
        const contentDOM=document.getElementById('content');
        contentDOM.innerHTML=content.text;
        contentDOM.style.color=content.color;
    }
    
    // 首次渲染页面
    renderApp(appState);
    
    // 修改标题文本
    dispatch({
        type:'UPDATE_TITLE_TEXT',
        text:'《Helllo World》'  
    });
    
    // 修改标题颜色
    dispatch({
        type:'UPDATE_TITLE_COLOR',
        color:'blue'
    });
    
    // 把新的数据渲染到页面上
    renderApp(appState);
    

    Redux(二):抽离store和监控数据变化

    抽离出store

    let appState={
        title:{
            text:'标题',
            color:'red'
        },
        content:{
            text:'内容',
            color:'blue'
        }
    };
    
    function stateChanger(state,action) {
        switch(action.type) {
            case 'UPDATE_TITLE_TEXT':
                state.title.text=action.text;
                break;
            case 'UPDATE_TITLE_COLOR':
                state.title.color=action.color;
                break;
            default:
                break;
        }   
    };
    
    function createStore(state,stateChanger) {
        const getState=()=>state;
        const dispatch=(action)=>stateChanger(state,action);
        return {
            getState,dispatch
        };
    };
    
    function renderApp(appState) {
        renderTitle(appState.title);
        renderContent(appState.content);
    };
    
    function renderTitle(title) {
        const titleDOM=document.getElementById('title');
        titleDOM.innerHTML=title.text;
        titleDOM.style.color=title.color;
    };
    
    function renderContent(content) {
        const contentDOM=document.getElementById('content');
        contentDOM.innerHTML=content.text;
        contentDOM.style.color=content.color;
    };
    
    const store=createStore(appState,stateChanger);
    
    renderApp(store.getState());
    
    store.dispatch({
        type:'UPDATE_TITLE_TEXT',
        text:'《Helllo World》'  
    });
    
    store.dispatch({
        type:'UPDATE_TITLE_COLOR',
        color:'blue'
    });
    
    renderApp(store.getState());
    
    // 针对每个不同的App,我们可以给createStore传入初始的数据appState,和一个描述数据变化的函数stateChanger,然后生成一个store。需要修改数据的时候通过store.dispatch,需要获取数据的时候通过store.getState。
    

    监控数据变化

    let appState={
        title:{
            text:'标题',
            color:'red'
        },
        content:{
            text:'内容',
            color:'blue'
        }
    };
    
    function stateChanger(state,action) {
        switch(action.type) {
            case 'UPDATE_TITLE_TEXT':
                state.title.text=action.text;
                break;
            case 'UPDATE_TITLE_COLOR':
                state.title.color=action.color;
                break;
            default:
                break;
        }   
    };
    
    function createStore(state,stateChanger) {
        const listeners=[];
        const subscribe=(listener)=>listeners.push(listener);
        const getState=()=>state;
        const dispatch=(action)=>{
            stateChanger(state,action)
            listeners.forEach((listener)=>listener())
        };
        return {
            getState,dispatch,subscribe
        };
    };
    
    function renderApp(appState) {
        renderTitle(appState.title);
        renderContent(appState.content);
    };
    
    function renderTitle(title) {
        const titleDOM=document.getElementById('title');
        titleDOM.innerHTML=title.text;
        titleDOM.style.color=title.color;
    };
    
    function renderContent(content) {
        const contentDOM=document.getElementById('content');
        contentDOM.innerHTML=content.text;
        contentDOM.style.color=content.color;
    };
    
    const store=createStore(appState,stateChanger);
    store.subscribe(()=>renderApp(store.getState()));
    
    renderApp(store.getState());
    
    store.dispatch({
        type:'UPDATE_TITLE_TEXT',
        text:'《Helllo World》'  
    });
    
    store.dispatch({
        type:'UPDATE_TITLE_COLOR',
        color:'blue'
    });
    

    Redux(三):纯函数

    // 一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。
    // 函数的返回结果只依赖于它的参数。
    

    Redux(四):共享结构的对象提高性能

    // 解决性能问题:完整代码
    let appState={
        title:{
            text:'标题',
            color:'red'
        },
        content:{
            text:'内容',
            color:'blue'
        }
    };
    
    function stateChanger(state,action) {
        switch(action.type) {
            case 'UPDATE_TITLE_TEXT':
                state.title.text=action.text;
                break;
            case 'UPDATE_TITLE_COLOR':
                state.title.color=action.color;
                break;
            default:
                break;
        }   
    };
    
    function createStore(state,stateChanger) {
        const listeners=[];
        const subscribe=(listener)=>listeners.push(listener);
        const getState=()=>state;
        const dispatch=(action)=>{
            stateChanger(state,action)
            listeners.forEach((listener)=>listener())
        };
        return {
            getState,dispatch,subscribe
        };
    };
    
    function renderApp(newAppState,oldAppState={}) {
        if(newAppState===oldAppState) {
            return;
        }
        console.log('render app...');
        renderTitle(newAppState.title,oldAppState.title);
        renderContent(newAppState.content,oldAppState.content);
    };
    
    function renderTitle(newTitle,oldTitle={}) {
        if(newTitle===oldTitle) {
            return;
        }
        console.log('render title...');
        const titleDOM=document.getElementById('title');
        titleDOM.innerHTML=newTitle.text;
        titleDOM.style.color=newTitle.color;
    };
    
    function renderContent(newContent,oldContent={}) {
        if(newContent===oldContent) {
            return;
        }
        console.log('render content...');
        const contentDOM=document.getElementById('content');
        contentDOM.innerHTML=newContent.text;
        contentDOM.style.color=newContent.color;
    };
    
    const store=createStore(appState,stateChanger);
    let oldState=store.getState();
    store.subscribe(()=>{
        const newState=store.getState();
        renderApp(newState,oldState);
        oldState=newState;
    })
    
    renderApp(store.getState());
    
    store.dispatch({
        type:'UPDATE_TITLE_TEXT',
        text:'《Helllo World》'  
    });
    
    store.dispatch({
        type:'UPDATE_TITLE_COLOR',
        color:'blue'
    });
    

    共享结构的对象

    // 浅拷贝
    const obj={a:1,b:2};
    const obj2={...obj};
    
    // 覆盖、拓展对象属性
    const obj={a:1,b:2};
    const obj2={...obj,b:3,c:4};
    

    优化性能

    // 修改stateChanger,让它修改数据的时候,并不会直接修改原来的数据state,而是产生上述的共享结构的对象
    function stateChanger(state,action) {
        switch(action.type) {
            case 'UPDATE_TITLE_TEXT':
                // 构建新的对象并且返回
                return {
                    ...state,
                    title:{
                        ...state.title,
                        text:action.text
                    }
                }
            case 'UPDATE_TITLE_COLOR':
               // 构建新的对象并且返回
               return {
                   ...state,
                   title:{
                       ...state.title,
                       color:action.color
                   }
               }
            default:
                // 没有修改,返回原来的对象
                return state;
        }   
    };
    
    // 因为stateChanger不会修改原来对象,而是返回对象,所以需要修改一下createStore。
    function createStore(state,stateChanger) {
        const listeners=[];
        const subscribe=(listener)=>listeners.push(listener);
        const getState=()=>state;
        const dispatch=(action)=>{
            // 覆盖原对象
            state=stateChanger(state,action)
            listeners.forEach((listener)=>listener())
        };
        return {
            getState,dispatch,subscribe
        };
    };
    

    Redux(五):不要问为什么的reducer

    // 合并appState和stateChanger
    function stateChanger(state,action) {
        if(!state) {
            return {
                title:{
                    text:'标题',
                    color:'红色'
                },
                content:{
                    text:'内容',
                    color:'蓝色'
                }
            }
        }
        switch(action.type) {
            case 'UPDATE_TITLE_TEXT':
                return {
                    ...state,
                    title:{
                        ...state.title,
                        text:action.text
                    }
                }
            case 'UPDATE_TITLE_COLOR':
               return {
                   ...state,
                   title:{
                       ...state.title,
                       color:action.color
                   }
               }
            default:
               return state;
        }   
    };
    

    Redux(六):Redux总结

    // 定义reducer只能是纯函数,功能是负责初始化state,和根据
    // state和action计算具有共享结构的新的state
    function reducer(state,action) {
        // 初始化state和switch case
    };
    
    // 生成store
    const store=createStore(reducer);
    
    // 监听数据变化重新渲染页面
    store.subscribe(()=>renderApp(store.getState()));
    
    // 首次渲染页面
    renderApp(store.getState());
    
    // 后面可以随意dispatch了,页面自动更新
    store.dispatch(...)
    

    React-redux(一):初始化工程

    // 用create-react-app新建一个工程,然后在src/目录下新建三个文件:Header.js、Content.js、ThemeSwitch.js
    // 修改Header.js
    import React, { Component, PropTypes } from 'react';
    
    class Header extends Component {
      render () {
        return (
          <h1>React.js 小书</h1>
        )
      }
    }
    
    export default Header
    
    // 修改ThemeSwitch.js
    import React, { Component, PropTypes } from 'react';
    
    class ThemeSwitch extends Component {
      render () {
        return (
          <div>
            <button>Red</button>
            <button>Blue</button>
          </div>
        )
      }
    }
    
    export default ThemeSwitch
    
    // 修改Content.js
    import React, { Component, PropTypes } from 'react';
    import ThemeSwitch from './ThemeSwitch'
    
    class Content extends Component {
      render () {
        return (
          <div>
            <p>React.js 小书内容</p>
            <ThemeSwitch />
          </div>
        )
      }
    }
    
    export default Content
    
    // 修改index.js
    import React, { Component, PropTypes } from 'react'
    import ReactDOM from 'react-dom'
    import Header from './Header'
    import Content from './Content'
    import './index.css'
    
    class Index extends Component {
      render () {
        return (
          <div>
            <Header />
            <Content />
          </div>
        )
      }
    }
    
    ReactDOM.render(
      <Index />,
      document.getElementById('root')
    )
    

    React-redux(二):结合context和store

    // index.js
    import React,{ Component,PropTypes } from 'react';
    import ReactDOM from 'react-dom';
    import Header from './Header';
    import Content from './Content';
    import './index.css';
    
    function createStore(reducer) {
        let state=null;
        const listeners=[];
        const subscribe=(listener)=>listeners.push(listener);
        const getState=()=>state;
        const dispatch=(action)=>{
            state=reducer(state,action)
            listeners.forEach((listener)=>listener())
        }
        dispatch({});
        return {
            getState,dispatch,subscribe
        }
    }
    
    const themeReducer=(state,action)=>{
        if(!state) {
            return {
                themeColor:'red'
            }
        }
        switch(action.type) {
            case 'CHANGE_COLOR':
                return {
                    ...state,themeColor:action.themeColor
                }
            default:
                return state
        }
    }
    
    const store=createStore(themeReducer)
    
    class Index extends React.Component {
      static childContextTypes={
          store:PropTypes.object
      }
    
      getChildContext() {
          return {
              store
          }
      }
    
      render () {
        return (
          <div>
            <Header />
            <Content />
          </div>
        )
      }
    }
    
    ReactDOM.render(
      <Index />,
      document.getElementById('root')
    )
    
    // Header.js
    import React,{ Component,PropTypes } from 'react';
    
    class Header extends React.Component {
        static contextTypes={
            store:PropTypes.object
        }
    
        constructor() {
            super();
            this.state={
                themeColor:''
            }
        }
    
        componentWillMount() {
            this._updateThemeColor()
        }
    
        _updateThemeColor() {
            const {store}=this.context;
            const state=store.getState();
            this.setState({
                themeColor:state.themeColor
            })
        }
    
        render() {
            return (
                <h1 style={{color:this.state.themeColor}}>标题</h1>
            )
        }
    }
    
    export default Header
    
    // Content.js
    import React,{ Component,PropTypes } from 'react';
    import ThemeSwitch from './ThemeSwitch';
    
    
    class Content extends React.Component {
      static contextTypes={
        store:PropTypes.object
      }
    
      constructor() {
        super();
        this.state={
            themeColor:''
        }
      }
    
      componentWillMount() {
          this._updateThemeColor()
      }
    
      _updateThemeColor() {
          const { store }=this.context;
          const state=store.getState();
          this.setState({ themeColor: state.themeColor })
      }
    
      render() {
        return (
          <div>
            <p style={{color:this.state.themeColor}}>内容</p>
            <ThemeSwitch />
          </div>
        )
      }
    }
    
    export default Content
    

    相关文章

      网友评论

        本文标题:React(四)

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