抛开 React 学习 React(2)
来源:互联网 发布:java创建日志文件 编辑:程序博客网 时间:2024/06/06 19:25
让我们继续第一部分没讲到的东西。 这次的文章主要是专注于如何重构我们的 todo list。首先,我们还没有正确地处理事件。现在,我们的组件根本就没有绑定任何事件。在 React 里面,数据流是从上往下,而事件流则是从下往上(In React data flows down while events move up)。也就是说,当事件触发的时候,我们应该沿着组件链,从下往上找其对应的回调函数。比如,我们的 ItemRow 函数应该调用一个从 props 传递下来的函数。
那么,我们怎么实现呢?下面是一个小尝试:
function ItemRow (props) { var className = props.completed ? 'item completed' : 'item'; return $('<li>') .on('click', props.onUpdate.bind(null, props.id)) .addClass(className) .attr('id', props.id) .html(props.text); }
在上面,我们给 list 元素绑定了一个事件。当点击他们的时候,onUpdate 函数就会被调用。可以看到, onUpdate 函数是从 props 传递下来的。
现在,我们不妨定义一个函数,他可以在创建元素的同时为其绑定事件。
function createElement (tag, attrs, children) { var elem = $('<', + tag + '>'); for (var key in attrs) { var val = attrs[key]; //如果传进来的attr属性数组中以on开头,那么为其绑定事件,否则当做普通属性处理 if(key.indexOf('on') === 0) { var event = key.substr(2).toLowerCase(); elem.on(event, val); } else { elem.attr(key, val); } } return elem.html(children); }
这样一来,我们的 ItemRow 函数可以写成这样:
function ItemRow (props) { var className = props.completed ? 'item completed' : 'item'; //调用上面的方法,在创建元素的同时绑定事件 return createElement('li', { id: props.id, class: props.className, onClick: props.onUpdate.bind(null, props.id) }, props.text) }
需要注意的是,React 中的 createElement 函数是创建了一个 js对象来表示 DOM 元素。这里让我们可以看看 React 中的 JSX 语法到底是怎样子的。
下面就是一个 JSX 例子:
return ( <div id='el' className='entry'> Hello </div>)
接着会转换成调用createElement:
var SomeElement = React.createElement('div', { id: 'el', className: 'entry' }, 'Hello')
然后调用 SomeElement 函数会返回一个像下面差不多的 js对象:
{ // ... type: 'div', key: null, ref: null, props: { children: 'Hello', className: 'entry', id: 'el' } }
想要了解JSX更多的话,请阅读 React Components, Elements, and Instances
回到我们的例子中,onUpdate 函数是从哪里来的?
首先来看看我们的 render 函数。他定义了一个 updateState 函数,然后通过 props 把这个函数传给 ItemList 组件。
function render (props, node) { function updateState (toggleId) { state.items.forEach(function (el) { if (el.id === toggleId) { el.completed = !el.completed } }) store.setState(state) } node.empty().append([ItemList({ items: props.items, onUpdate: updateState })]) }
然后,ItemList 函数会把 onUpdate 传递到每个 ItemRow。
function extending (base, item) { return $.extend({}, item, base) } function ItemsList (props) { return createElement('ul', {}, props.items .map(extending.bind(null, { onUpdate: props.onUpdate })) .map(ItemRow)) }
通过以上我们实现了:数据流是沿着组件链从上往下流,而事件流是从下往上。这就意味着我们可以把定义在全局的监听器移除掉(用来监听点击 item 的时候改变其状态的监听器)。那么,我们把这个函数移到了 render 函数里面,也就是前面所讲的 updateState。
我们还可以重构
现在我们把 input 和 button 从 HTML 标签变成了函数。因此,我们整个 HTML 文件就只剩下一个 div。
<div id="app"></app>
因此,我们可以很简便地创建 input 元素,就这样:
var input = createElement('input', {id: 'input'})
同样地,我们也可以把监听 searchBar button 点击事件的全局函数放在我们的 SearchBar 函数里面。SearchBar 函数会返回一个 input 和一个 button 元素,他会通过 props 传进来的回调函数来处理点击事件。
function SearchBar(props) { function onButtonClick (e) { var val = $('#input').val() $('#input').val('') props.update(val) e.preventDefault() } var input = createElement('input', {id: 'input'}) // move listener to here var button = createElement('button', { id: 'add', onClick: onButtonClick.bind(null) }, 'Add') return createElement('div', {}, [input, button]) }
在上面,我们的 render 函数在调用 SearchBar 的同时需要传递正确的 props 参数。
在我们重构 render 函数之前,让我们想想 re-render 应该在哪里调用才是正确的。首先,忽略我们的 store,把注意力集中在如何在一个 high level component 中处理 state。
目前为止,所有的函数都是 stateless 的。接下来我们会创建一个函数,他会处理 state,以及在适当的时候更新子组件(children)。
Container Component
让我们来创建一个 high level container 吧。与此同时,为了更好理解,你可以阅读Presentational and Container Component 。
首先,我们给这个 container component 取名为 App。他所做的事情就是调用 SearchBar 和 ItemList 函数。现在,我们继续重构 render 函数。其实就是把代码移到 App 里面去而已。
我们不妨先来看看 render 现在是怎样子的:
function render (component, node) { node.empty().append(component) } render(App(state), $('#app'))
我们的 render 函数只是简单地把整个应用渲染到某个 HTML 节点。但是,React 的实现会比这个复杂一点,而我们仅仅把一棵 element tree 添加到指定的节点中而已。但是抽象起来理解的话,这个已经足够了。
现在,我们的 App 函数其实就是我们旧的 render 函数,除了 DOM 操作被删掉。
现在,我们的 App 函数其实就是我们旧的 render 函数,除了 DOM 操作被删掉。
function App (props) { function updateSearchBar (value) { state.items.push({ id: state.id++, text: value, completed: false }) } function updateState (toggleId) { state.items.forEach(function (el) { if (el.id === toggleId) { el.completed = !el.completed } }) store.setState(state) } return [ SearchBar({update: updateSearchBar}), ItemsList({items: props.items, onUpdate: updateState}) ] }
我们还需要改进一样东西:我们访问的 store 是全局的,并且重新渲染的话需要调用 setState 函数。
我们现在来重构 App 函数,使得他的子组件重新渲染的是不需要调用 store。那么应该要怎么实现呢?
首先我们暂时不考虑 store,而是想想怎么调用 setState 函数,使得组件和他的子组件重新渲染。
我们需要跟踪这个 high level component 当前的状态,并且只要 setState 一调用,就立马重新渲染。下面是一个简单的实现:
function App (props) { function getInitialState (props) { return { items: [], id: 0 } } var _state = getInitialState(), _node = null function setState (state) { _state = state render() } // .. }
我们通过调用 getInitialState 来初始化我们的 state,然后每当使用 setState 来更新状态的时候,我们会调用 render 函数。
而 render 函数要么创建一个 node,要么简单地更新 node,只要 state 发生改变。
// naive implement of render function render () { var children = [ SearchBar({update: updateSearchState}), ItemList({ items: _state.items, onUpdate: updateState }) ] if (!_node) { return _node = createElement('div', {class: 'top'}, children) } else { return _node.html(children) } }
很显然,这对性能来说是不好的。需要知道的是,React 中的 setState 不会渲染整个应用,而是组件和他的子组件。
下面是 render 函数的最新代码,我们调用 App 时不需要带任何参数,只是需要在 App 里面简单地调用 getInitialState 来初始化 state。
function render(component, node) { node.empty().append(component) } render(App(), $('#app'))
继续改进
如果有一个函数,他会返回一个对象。这个对象包含了 setState 函数,还能够区分传进来 props 和 组件本身自己的 state。
差不多就像下面这样:
var App = createClass({ updateSearchState: function (string) { /*...*/ }, updateState: function (obj) { /*... */ }, render: function () { var children = [ SearchBar({ updateSearchState: this.updateSearchState }), ItemsList({ items: this.state.items, onUpdate: this.updateState }) ] return createElement('div', {class: 'top'}, children) } })
- 抛开 React 学习 React(2)
- 抛开 React 学习 React(1)
- React + Redux 入门(一):抛开 React 学 Redux
- react-redux学习笔记-2-react-redux
- React学习2
- React学习笔记(2)
- react-native学习(2)
- react学习--2
- react redux 学习2
- react学习日志2
- react 学习
- react学习
- react学习
- react学习
- react 学习
- React学习
- React学习
- react学习
- 【Hibernate】hql小结
- POJ 1573 Robot Motion (模拟)
- POJ 2376 Cleaning Shifts 区间贪心
- 自定义圆形头像CirclemageView
- 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。时间复杂度都是O(1)
- 抛开 React 学习 React(2)
- 如何查看新买手机是否翻新机?
- Kaggle项目Digit Recognizer实现(一):三层卷积神经网络
- 编译完android源码并且已经make sdk之后,重启不能调用emulator命令解决方案
- python学习笔记——基础篇(3):函数的定义
- java的重载、覆盖和隐藏的区别
- HTTP 中 GET 与 POST 的区别
- 《浅谈架构之路:前后端分离模式》
- 在Activity里调用Service中的方法以及MediaPlay类的使用