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