<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>namda-on.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 30 Jul 2021 01:59:40 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>namda-on.log</title>
            <url>https://images.velog.io/images/namda-on/profile/508a98a4-f73c-4786-90a4-7f9ec238b12d/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. namda-on.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/namda-on" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[JavaScript Map 과 Object 의 차이]]></title>
            <link>https://velog.io/@namda-on/JavaScript-Map-%EA%B3%BC-Object-%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@namda-on/JavaScript-Map-%EA%B3%BC-Object-%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Fri, 30 Jul 2021 01:59:40 GMT</pubDate>
            <description><![CDATA[<h3 id="javascript-type">Javascript Type</h3>
<p>Javascript에는 7개의 타입(ES6 기준으로)이 존재하는데 해당 타입들은 크게 원시타입과 객체 타입으로 분리된다. </p>
<p><strong>원시타입</strong></p>
<ul>
<li>boolean</li>
<li>null</li>
<li>undefined</li>
<li>number</li>
<li>string</li>
<li>symbol</li>
</ul>
<p><strong>객체타입 (object/reference type)</strong></p>
<ul>
<li>object</li>
</ul>
<h2 id="object">Object</h2>
<p>자바스크립트 객체(object) 기반의 스크립트 언어이며 자바스크립트를 이루고 있는 거의 모든 것이 객체라 할 수 있다. 원시 타입을 제외한 나머지 값들(함수, 배열, 정규표현식 등)은 모두 객체이다. </p>
<p>자바스크립트 객체는 Key와 Value로 구성된 property 들의 집합이다. 프로퍼티의 값으로 자바스크립트에서 사용할 수 있느 모든 값을 사용할 수 있다. 객체는 데이터를 의미하는 property와 데이터를 참조하고 동작을 의미하는 method로 구성된 집합이다. </p>
<p>객체는 데이터와 그 데이터에 관련된 동작을 모두 포함할 수 있기 때문에 데이터와 동작을 하나의 단위로 구조화할 수 있어 유용하다. </p>
<h3 id="프로퍼티">프로퍼티</h3>
<p><strong>key의 제약조건</strong></p>
<p>프로퍼티 키는 일반적으로 문자열을 지정한다. 프로퍼티 키에 문자열이나 symbol 이외의 값을 지정하면 암묵적으로 타입이 변환되여 문자열이 된다.  프로퍼티 키는 문자열이므로 따옴표를 사용하는데 자바스크립트 내의 예약어가 아닌 경우 따옴표를 생략할 수 있다. </p>
<p>→ 프로퍼티의 키는 문자열 또는 symbol이다. </p>
<h3 id="value">value</h3>
<p> 프로퍼티 값은 모든 값과 표현식이 올 수 있으며 프로퍼티 값이 함수인 경우 이를 메소드라 한다. </p>
<h3 id="반복">반복</h3>
<p>for-in 문을 사용하면 객체에 포함된 모든 포르퍼티에 대해 루프를 수행할 수 있다.</p>
<pre><code class="language-jsx">const person = { 
    &#39;name&#39; : &#39;namda&#39;,
    &#39;gender&#39; : &#39;male&#39;,
};

for(const prop in person){
    console.log(prop + &quot; : &quot; + person[prop]);
}

/*
name : namda
gender : male 
*/</code></pre>
<p>→ key들의 순서는 보장되지 않는다. </p>
<p>(for of로는 반복문을 돌 수 없다.) </p>
<h2 id="map">Map</h2>
<p>자바스크립트 맵은 Object와 마찬가지로 key-value 로 구성된 data구조이며 해당 key들의 insertion에 대한 순서를 기억한다. key 값으로 어떠한 값도 사용할 수 있는데 object에서는 key 값으로 사용할 수 없었던 number와 같은 원시타입도 사용가능하다. </p>
<h3 id="반복-1">반복</h3>
<p>for-of를 통해 반복할 수 있다. </p>
<pre><code class="language-jsx">const person = new Map();
person.set(&quot;name&quot;, &quot;namda&quot;);
person.set(&quot;gender&quot;, &quot;male&quot;);

for(const prop of person){
    console.log(prop);
}

/*
[&#39;name&#39;, &#39;namda&#39;]
[&#39;gender&#39;, &#39;male&#39;]
*/</code></pre>
<table>
<thead>
<tr>
<th></th>
<th>Map</th>
<th>Object</th>
</tr>
</thead>
<tbody><tr>
<td>key Types</td>
<td>Map은 어떠한 값도 key로 가질 수 있다.</td>
<td>Object의 key는 String 또는 Symbol이어야 한다.</td>
</tr>
<tr>
<td>key Order</td>
<td>Map은 삽입한 순서대로 key가 정렬되어 있다. Map을 iterate할 경우 삽입된 순서대로 나온다.</td>
<td>Object의 key들은 현재는 순서대로 정렬되기는 하지만, 해당 순서들은 항상 보장되는 것은 아니며 Object property 순서에 의존하지 않는 것이 좋다.</td>
</tr>
<tr>
<td>Iteration</td>
<td>Map 은 이터러블하며 direct하게 iterate 할 수 있다.</td>
<td>Object는 iteration 프로토콜을 사용하지 않으며 따라서 direct 하게 iterate를 할 수 없다. (Object.keys 나 Object.entries 또는 for..in 구문을 사용하여야 한다)</td>
</tr>
<tr>
<td>performance</td>
<td>빈번한 key-value pair의 삽입과 삭제시 더 높은 performance를 가진다. get에서 object에 비해 performance가 느리다는 이야기가 있다.</td>
<td>key-value pair의 빈번한 추가 및 삭제에 대한 최적화가 되어있지 않다.</td>
</tr>
<tr>
<td>Serialization and parsing</td>
<td>No native support for serialization or parsing</td>
<td>JSON.stringify() 및 JSON.parse()를 지원한다.</td>
</tr>
</tbody></table>
<h3 id="map-data를--json으로-변환하기">Map data를  JSON으로 변환하기</h3>
<p>JSON.stringify 와 JSON.parse는 각각 <code>replacer</code> 와 <code>reviver</code> 를 두번째 인자로 지원한다. replacer와 reviver를 통해 Map data를 파싱할 수 있다. </p>
<pre><code class="language-jsx">function replacer(key, value) {
  if(value instanceof Map) {
    return {
      dataType: &#39;Map&#39;,
      value: Array.from(value.entries()), // or with spread: value: [...value]
    };
  } else {
    return value;
  }
}

function reviver(key, value) {
  if(typeof value === &#39;object&#39; &amp;&amp; value !== null) {
    if (value.dataType === &#39;Map&#39;) {
      return new Map(value.value);
    }
  }
  return value;
}

const originalValue = new Map([[&#39;a&#39;, 1]]);
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
</code></pre>
<p><a href="https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map">https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map</a></p>
<h3 id="object--map-사용-결정">Object  Map 사용 결정</h3>
<ol>
<li><p>key의 타입이 run time 까지 정해지지 않은 경우 </p>
<p> → Map이 더 안정성을 가진다 ( key 타입으로 모든 값이 가능하므로) </p>
</li>
<li><p>key 값으로 원시타입을 사용할 경우</p>
</li>
<li><p>각각의 element에 대한 연산이 필요할 경우 Object를 사용하자 </p>
</li>
</ol>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://poiemaweb.com/js-data-type-variable">https://poiemaweb.com/js-data-type-variable</a>
<a href="https://poiemaweb.com/js-object">https://poiemaweb.com/js-object</a></p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map</a></p>
<p><a href="https://stackoverflow.com/questions/18541940/map-vs-object-in-javascript">https://stackoverflow.com/questions/18541940/map-vs-object-in-javascript</a></p>
<p><a href="https://medium.com/front-end-weekly/es6-map-vs-object-what-and-when-b80621932373">https://medium.com/front-end-weekly/es6-map-vs-object-what-and-when-b80621932373</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Intersection Observer를 활용한 Lazy Rendering - TypeScript]]></title>
            <link>https://velog.io/@namda-on/Intersection-Observer%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-Lazy-Rendering</link>
            <guid>https://velog.io/@namda-on/Intersection-Observer%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-Lazy-Rendering</guid>
            <pubDate>Thu, 24 Dec 2020 17:24:06 GMT</pubDate>
            <description><![CDATA[<p>부스트 캠프에서 가계부 서비스를 개발하며 겪었던 난관 중 하나를 해결하는 과정에 대해 공유하고자 합니다. 프로젝트를 진행하며, 한 달간의 거래내역 페이지에서 거래내역 data의 양이 많을 때 랜더링 속도가 매우 느린 것을 관찰하였고 이를 개선하고자 <strong>무한스크롤</strong>을 구현하려고 도전하였습니다. Intersection Observer와 관련된 글들을 찾아보며 Typescript로 작성된 글이 없어서 작성하였습니다. </p>
<p><strong>문제상황</strong></p>
<ul>
<li>Server로 부터 한달간의 거래내역 data를 받아온다.</li>
<li>한달간의 거래내역 data가 redux Store에 저장되고 이를 통해 다른 컴포넌트의 랜더링에 사용된다.</li>
<li>한달간의 총 수입, 지출 등을 api 분리를 하지 않아 전체 data를 모두 가져오는 상황</li>
<li><strong>data 자체는 모두 가지고 있는 상태에서 Rendering 자체만 끊어서 하자!</strong> </li>
</ul>
<p>** 기술스택**</p>
<ul>
<li>React, Typescript, Styled-component, Redux 등  </li>
</ul>
<h3 id="scroll-event">Scroll Event</h3>
<p> 처음에 시도했던 방식은 Scroll Event를 등록하여 화면 상의 scroll Top의 비율을 통해 일정 비율을 넘었을 때 랜더링하는 방식으로 구현하려 하였습니다. (지금 생각해보면 scroll 의 위치가 가장 밑으로 내려왔을 때 event를 발생시키는 방식이 더 나았을 것 같네요..) 
 <img src="https://images.velog.io/images/namda-on/post/a0d340a5-1ee8-4885-a49d-320f9b7ce4ab/image.png" alt="">
 이때 발생가능한 문제는 해당하는 기준이 device 마다 차이가 있으면 동작의 일관성이 유지되지 않는다는 점이였습니다. </p>
<p> 추가적으로, 부스트캠프에서 저희 프로젝트 팀이 관심있게 가졌던 주제가 최적화였는데 scroll event 방식은 한 번의 스크롤에 scroll event가 여러번 발생하므로 <code>throttle</code> 또는 <code>debounce</code>를 적용하여 이러한 event가 무한히 발생하지 않도록 관리해줘야 한다는 점이었는 데, 이점에서 scroll event 자체가 매력적으로 느껴지지 않았습니다. </p>
<h2 id="intersection-observer">Intersection Observer</h2>
<p>scroll event의 대안을 찾다가 <code>Intersection Observer</code> 라는 것을 알게 되었습니다. </p>
<h3 id="intersection-observer-란">Intersection Observer 란?</h3>
<p><img src="https://images.velog.io/images/namda-on/post/cfe1e959-0a91-4cef-8ef3-03ef79957529/image.png" alt=""></p>
<p><code>IntersectionObserver(교차 관찰자 API)</code>는 타겟 엘레멘트와 타겟의 부모 혹은 상위 엘레멘트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API입니다.</p>
<p><code>ViewPort</code> 는 사용자에게 보여지는 화면이라 생각하면 쉽습니다. 사용자의 화면과 element가 얼마나 교차되는지 비율이 계산되고, 그 비율을 <code>threshold</code>를 통해 콜백함수의 trigger 기준으로 정할 수 있습니다. </p>
<p>Intersection Observer 에 대한 설명은 MDN 과 다른 글들을 참고하시면 쉽게 이해하실 수 있습니다! </p>
<h3 id="어떤-기준으로-적용할-것인가">어떤 기준으로 적용할 것인가?</h3>
<ul>
<li>저희 프로젝트에서 거래내역 data를 랜더링할 때 일자별로 묶어서 랜더링을 해야했기 때문에 어떤 element를 observe 할지에 대한 기준을 정할 필요성이 있었습니다.
<img src="https://images.velog.io/images/namda-on/post/599b9c74-efab-41d1-8f3c-78cd7f7c470d/image.png" alt=""></li>
<li>제가 개발과정에서 기준으로 삼았던 점은 <strong>5일 단위</strong>로 끊어서 랜더링하는 방식으로 lazy Renderig을 구현하기로 결정하였습니다. (기준에 있어서 최적의 방식을 정하진 못한 것 같습니다)</li>
</ul>
<h3 id="기존의-코드">기존의 코드</h3>
<pre><code>import React from &#39;react&#39;;
import { useSelector } from &#39;react-redux&#39;;
import TransactionListItem from &#39;@/components/transaction/ListItem&#39;;
import { RootState } from &#39;@modules/index&#39;;
import { TransactionModel } from &#39;@/commons/types/transaction&#39;;
import EmptyStateComponent from &#39;@/components/transaction/EmptyState&#39;;
import * as S from &#39;./styles&#39;;

const TransactionListContainer = (): JSX.Element =&gt; {
  const { transaction } = useSelector((state: RootState) =&gt; state);

  return (
    &lt;&gt;
      {transaction.transactionDetailsByDate.length !== 0 ? (
        transaction.transactionDetailsByDate.map(([date, transactionDetails]) =&gt; (
          &lt;S.DateContainer key={`transaction_box_${date}`}&gt;
            &lt;S.DateLabel&gt;{date}일&lt;/S.DateLabel&gt;
            {transactionDetails.map((transactionDetail) =&gt; (
              &lt;S.TransactionListItemWrapper&gt;
                &lt;TransactionListItem
                  key={`transaction_${transactionDetail.tid}`}
                  transaction={transactionDetail}
                /&gt;
              &lt;/S.TransactionListItemWrapper&gt;
            ))}
          &lt;/S.DateContainer&gt;
        ))
      ) : (
        &lt;EmptyStateComponent /&gt;
      )}
    &lt;/&gt;
  );
};

export default TransactionListContainer;</code></pre><ul>
<li>기존의 코드는 Redux Store에 저장된 transaction 정보를 가져와서 바로 랜더링하는 방식이었습니다. </li>
</ul>
<h3 id="랜더링할-list를-따로-상태로-분리">랜더링할 list를 따로 상태로 분리</h3>
<ul>
<li>Store 에서 가져온 data 를 바로 랜더링하면 안되므로 따로 상태를 두어 그 상태에 끊어서 list를 넘겨주는 방식으로 변경하였습니다. <pre><code>const [renderedTransaction, setRenderedTransaction] = useState([] as [number, TransactionModel[]][]);</code></pre></li>
<li>빈배열의 뒤에 정의된 타입은 Store에서 가져오는 data의 형식을 정의</li>
</ul>
<h3 id="끊어서-랜더링-되는-기준을-적용">끊어서 랜더링 되는 기준을 적용</h3>
<ul>
<li>5일을 기준으로 랜더링을 정의했으므로 <code>maxLength</code>를 store에서 가져온 거래내역 list에서 구하고 <code>useRef</code>를 이용하여 <code>length</code> 를 정의하여 현재 랜더링된 상태(얼마나 랜더링이 되었는지)를 계산 수 있도록 설정<pre><code>const length = useRef(1);
const maxLength = transaction.aggregationByDate.length / 5;
</code></pre></li>
</ul>
<pre><code>- 초기의 data가 들어왔을 때 5개의 data를 랜더링에 사용할 상태에 설정</code></pre><p>  useEffect(() =&gt; {
    if (!transaction.loading) {
      length.current = 1;
      if (transaction.transactionDetailsByDate.length &lt; 5) {
        setRenderedTransaction(transaction.transactionDetailsByDate);
      } else {
        setRenderedTransaction(transaction.transactionDetailsByDate.slice(0, 5));
      }
    }
  }, [transaction]);</p>
<pre><code>
### Intersection Observer가 observe할 target을 정의
- useRef를 통해 target을 참조하도록 했습니다.
- 이때, 코드를 보면 ref가 랜더링 되는 모든 `DateContainer` 에 대해 이루어진다는 것을 발견할 수 있는데 제 생각에는 계속해서 컴포넌트가 랜더링 되면서 기존의 ref가 덮어씌워지고 마지막에 랜더링이 되는 element에 ref에 대한 참조가 이루어지는 것 같습니다.
</code></pre><p>  const target = useRef<HTMLDivElement>(null);</p>
<p>  ...</p>
<p>   return (
    &lt;&gt;
      {transaction.transactionDetailsByDate.length !== 0 ? (
        renderedTransaction.map(([date, transactionDetails]) =&gt; (
          &lt;S.DateContainer key={<code>t_box_${date}${ren}</code>} ref={target}&gt;
            &lt;S.DateLabel&gt;{date}일&lt;/S.DateLabel&gt;
            {transactionDetails.map((transactionDetail) =&gt; (
              &lt;S.TransactionListItemWrapper key={<code>t_Wrap${transactionDetail.tid}</code>}&gt;
                &lt;TransactionListItem
                  key={<code>t_${transactionDetail.tid}</code>}
                  transaction={transactionDetail}
                /&gt;
              &lt;/S.TransactionListItemWrapper&gt;
            ))}
          &lt;/S.DateContainer&gt;
        ))
      ) : (
        <EmptyStateComponent />
      )}
    &lt;/&gt;
  );</p>
<pre><code>
### Intersection Observer 정의 및 동작 정의
**Intersection Observer 정의**
- threshold는 0.5로 설정하였습니다. (element의 50%가 viewport에 보이면 callback 함수가 실행된다.)
- cleanup 함수를 통해 observer의 연결을 끊어줬습니다. 
</code></pre><p>  useEffect(() =&gt; {
    let observer: IntersectionObserver;
    if (target.current) {
      observer = new IntersectionObserver(onIntersect, { threshold: 0.5 });
      observer.observe(target.current as Element);
    }
    return () =&gt; observer &amp;&amp; observer.disconnect();
  }, [transaction, renderedTransaction]);</p>
<pre><code>
**교차시 발생할 동작 정의**
- 교차가 발생했을 때(`entry.isIntersecting === true`) 
- 현재 랜더링된 상태(`length`)가  `maxLength` 보다 크지 않다면 observer를 unobserve 하고 랜더링할 상태에 배열 5개를 추가하였습니다. 
</code></pre><p>  const changeExtraTransaction = () =&gt; {
    const newrenderedTransaction = renderedTransaction.concat(
      transaction.transactionDetailsByDate.slice(5 * length.current, 5 * length.current + 5),
    );
    length.current += 1;
    setRenderedTransaction(newrenderedTransaction);
  };</p>
<p>  const onIntersect: IntersectionObserverCallback = (entries, observer) =&gt; {
    entries.forEach((entry) =&gt; {
      if (entry.isIntersecting &amp;&amp; length.current &lt; maxLength) {
        observer.unobserve(entry.target);
        changeExtraTransaction();
      }
    });
  };</p>
<pre><code>

### 전체코드</code></pre><p>import React, { useCallback, useEffect, useRef, useState } from &#39;react&#39;;
import { useSelector } from &#39;react-redux&#39;;
import TransactionListItem from &#39;@/components/transaction/ListItem&#39;;
import { RootState } from &#39;@modules/index&#39;;
import { TransactionModel } from &#39;@/commons/types/transaction&#39;;
import EmptyStateComponent from &#39;@/components/transaction/EmptyState&#39;;
import * as S from &#39;./styles&#39;;</p>
<p>const TransactionListContainer = (): JSX.Element =&gt; {
  const { transaction } = useSelector((state: RootState) =&gt; state);
  const length = useRef(1);
  const target = useRef<HTMLDivElement>(null);
  const [renderedTransaction, setRenderedTransaction] = useState(
    [] as [number, TransactionModel[]][],
  );
  const maxLength = transaction.aggregationByDate.length / 5;</p>
<p>  useEffect(() =&gt; {
    if (!transaction.loading) {
      length.current = 1;
      if (transaction.transactionDetailsByDate.length &lt; 5) {
        setRenderedTransaction(transaction.transactionDetailsByDate);
      } else {
        setRenderedTransaction(transaction.transactionDetailsByDate.slice(0, 5));
      }
    }
  }, [transaction]);</p>
<p>  const changeExtraTransaction = () =&gt; {
    const newrenderedTransaction = renderedTransaction.concat(
      transaction.transactionDetailsByDate.slice(5 * length.current, 5 * length.current + 5),
    );
    length.current += 1;
    setRenderedTransaction(newrenderedTransaction);
  };</p>
<p>  const onIntersect: IntersectionObserverCallback = (entries, observer) =&gt; {
    entries.forEach((entry) =&gt; {
      if (entry.isIntersecting &amp;&amp; length.current &lt; maxLength) {
        observer.unobserve(entry.target);
        changeExtraTransaction();
      }
    });
  };</p>
<p>  useEffect(() =&gt; {
    let observer: IntersectionObserver;
    if (target.current) {
      observer = new IntersectionObserver(onIntersect, { threshold: 0.5 });
      observer.observe(target.current as Element);
    }
    return () =&gt; observer &amp;&amp; observer.disconnect();
  }, [transaction, renderedTransaction]);</p>
<p>  return (
    &lt;&gt;
      {transaction.transactionDetailsByDate.length !== 0 ? (
        renderedTransaction.map(([date, transactionDetails]) =&gt; (
          &lt;S.DateContainer key={<code>t_box_${date}${ren}</code>} ref={target}&gt;
            &lt;S.DateLabel&gt;{date}일&lt;/S.DateLabel&gt;
            {transactionDetails.map((transactionDetail) =&gt; (
              &lt;S.TransactionListItemWrapper key={<code>t_Wrap${transactionDetail.tid}</code>}&gt;
                &lt;TransactionListItem
                  key={<code>t_${transactionDetail.tid}</code>}
                  transaction={transactionDetail}
                /&gt;
              &lt;/S.TransactionListItemWrapper&gt;
            ))}
          &lt;/S.DateContainer&gt;
        ))
      ) : (
        <EmptyStateComponent />
      )}
    &lt;/&gt;
  );
};</p>
<p>export default TransactionListContainer;</p>
<p>```</p>
<h3 id="결과">결과</h3>
<p><img src="https://images.velog.io/images/namda-on/post/16fc545e-a765-4528-a082-513245cdcad9/image.png" alt="">
<img src="https://images.velog.io/images/namda-on/post/da029ac1-538b-481e-8808-ecf41533889c/image.png" alt=""></p>
<ul>
<li>약 800개의 거래내역 data를 가지고 chrome 개발자 도구로 performance를 체크하였을 때 위와같은 변화를 보였습니다. (체감상으로도 훨씬 빨라졌다) </li>
</ul>
<h3 id="고려할사항">고려할사항</h3>
<ul>
<li>Intersection Observer API 는 IE에 지원되지 않습니다.</li>
<li><blockquote>
<p>pollyfill 을 적용시켜주자 </p>
</blockquote>
</li>
</ul>
<p><strong>참고자료</strong></p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Intersection Observer MDN</a></li>
<li><a href="https://developers.google.com/web/updates/2016/04/intersectionobserver">https://developers.google.com/web/updates/2016/04/intersectionobserver</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>