ref한 table에 자꾸 null값이 뜨네요


(weaklion) #1

class Calendar extends Component {
constructor(props) {
super(props);
this.Tableref = React.createRef();
this.YMref = React.createRef();
this.buildCalendar.bind(this);
}
state = {
today: new Date(),
year: “”,
month: “”
};

prevCalendar = () => {
let { today } = this.state;
today = new Date(
today.getFullYear(),
today.getMonth() - 1,
today.getDate()
);
this.buildCalendar();
};
nextCalendar = () => {
let { today } = this.state;
today = new Date(
today.getFullYear(),
today.getMonth() + 1,
today.getDate()
);
this.buildCalendar();
};
buildCalendar() {
let { today } = this.state;
const nMonth = new Date(today.getFullYear(), today.getMonth(), 1); //이번 달의 첫째날
const lastDate = new Date(today.getFullYear(), today.getMonth() + 1, 0); //이번달의 마지막날
this.setState({
year: today.getFullYear(),
month: today.getMonth() + 1
});
debugger;
//기존 테이블의 줄 칸 삭제
while (this.Tableref.current.rows.length > 2) {
this.Tableref.current.deleteRow(this.Tableref.rows.length - 1);
}
let row = null;
row = this.Tableref.current.insertRow();
let cnt = 0;
//1일이 시작되는 칸에 맞춤
for (let i = 0; i < nMonth.getDay(); i++) {
var cell = row.insertCell();
cnt = cnt + 1;
}
for (let i = 1; i < lastDate.getDate(); i++) {
cell = row.insertCell();
cell.innerHTML = i;
cnt = cnt + 1;
if (cnt % 7 === 0)
//1주일은 7일
row = this.Tableref.current.insertRow();
}
}

render() {
const { prevCalendar, nextCalendar, buildCalendar } = this;
const { month, year } = this.state;

return (
  <div>
    <table ref={this.Tableref} border="3" align="center">
      <tr>
        <td>
          <label onClick={prevCalendar()} />
        </td>
        <td ref={this.YMref} colspan="5" align="center">
          {year}년{month}월{" "}
        </td>
        <td>
          <label onClick={nextCalendar()} />
        </td>
      </tr>
      <tr>
        <td align="center">일</td>
        <td align="center">월</td>
        <td align="center">화</td>
        <td align="center">수</td>
        <td align="center">목</td>
        <td align="center">금</td>
        <td align="center">토</td>
      </tr>
      {buildCalendar}
    </table>
  </div>
);

}
}
export default Calendar;

리액트로 달력을 만들어 보려고 하는데 ref한 this.tableref.rows.current.length에서 null값이 뜨네요
디버그 해보니 current에 null값이 있구요.
어떻게 하면 저기에 값을 넣을 수 있을까요…?
클래스 컴포넌트 안에서 함수를 정의해서 문제인가요?
아니면 ref값을 못받아오는게 문제일까요…
해결책을 주시면 감사하겠습니다.
.


(서재원) #2

코드의 들여쓰기가 전부 깨져서 보여서 문제점을 전부 파악하기는 힘드나, 눈에 띄는 문제점이라도 지적해 드리자면:

this.buildCalendar.bind(this);

  1. this 값을 bind한 buildCalendar 함수를 방치하고 계십니다.
    아마 의도하신 건 다음과 같은 코드인 걸로 추정됩니다.
    this.buildCalendar = this.buildCalendar.bind(this);

TMI:
Function.prototype.bind 혹은 bind 메서드는 this 값으로 주어진 함수를 호출하는 Bound Function 객체를 만들어 반환하는 함수입니다. 자바스크립트에서 가시성을 띄는 특정 변수나 mutable 한 값을 수정하지 않습니다.

  1. Tableref, YMref 보다는 camel-case를 살린 다음과 같은 이름을 추천해 드립니다.
    Tableref -> tableRef
    YMref -> YMRef

  2. Class field syntax를 점진적으로 사용하지 말아주세요.
    class field를 사용하실 거면 코드베이스 전체에서 사용하시는 걸 권장해드립니다.
    코드의 유지보수성을 해치는 좋지 못한 행위 중 하나가 일관성 없는 코드 작성하기 입니다.

class Calendar extends Component {
  tableRef = React.createRef();
  YMRef = React.createRef();
  ~~~~
}
  1. 바인딩이 mutable 하지 않는 경우에는 const를 사용해 주세요.
    let { today } = this.state; -> const { today } = this.state;

  2. todaythis.state.today가 아닙니다.

prevCalendar = () => {
  let { today } = this.state;
  today = new Date(
    today.getFullYear(),
    today.getMonth() - 1,
    today.getDate()
  );
  this.buildCalendar();
};

위 코드를 보아하니, this.state.today 프로퍼티를 수정하고 싶어하시는 거로 보입니다.
이런 코드에서는 크게 두 가지 문제점을 찾을 수 있습니다.

  • 컴포넌트의 상태를 직접적으로 수정하는 문제.
    컴포넌트의 상태를 직접 수정하는 행위는 React의 주된 무기 중 하나인 컴포넌트의 반응성(Reactivity)를 상실하는 행위입니다. 상태를 수정하셔야 할 때에는 Component.prototype.setState(a.k.a setState)를 사용하세요.
  • 의미없는 mutable 한 바인딩을 만드는 문제.
    위 코드에서 today 변수는 그 값을 수정하는 로직을 제외하면, 어떤 로직도 참조하지 않고 있습니다.
    즉, 고립된 변수이지요. 이런 변수는 지우셔야 합니다(this.state.todaytoday는 다른 바인딩입니다. today를 수정하신다고 해서 this.state.today가 수정되는 건 아니에요.).
  1. God Object
    God Object는 OOP에서 너무 많은 정보를 들고 있거나, 너무 많은 일을 하는 잘못된 객체를 가르키는 말 입니다.
    보통 God Object는 코드(혹은 객체)의 결합도는 높이고 응집도는 낮춤으로서 유지보수는 물론 프로젝트 전반적으로 엄청난 피해를 입힙니다. 그래서 안티 패턴이라 불리며 기피되지요.
    React가 OOP적인 프레임워크는 아니지만, Calendar 컴포넌트의 상태는 가히 God Object라 불러도 될 정도로 너무 많은(그리고 쓸모없는) 정보를 담고 있습니다.
    Date 객체는 전반적으로 날짜와 관련된 모든 정보와 정말 많은(거의 대부분의) 연산들을 가지고 있는 객체입니다. 다시말해, Date 객체는 날짜와 관련된 정보를 처리하는 일에 특화된 객체이지요. 그러나 작성자님은 이 객체를 Calendar 컴포넌트의 state에 가지고 있으심에도 불구하고 stateyearmonth라는, 불필요한 정보를 심지어 문자열로 저장해 두셨습니다(문자열은 날짜 계산을 위한 연산을 단 한 개도 가지고 있지 않습니다.). 말이 길어지긴 했는데, 결론은 간단합니다. 상태를 다음과 같이 바꾸시는 걸 권장해 드립니다.
state = {
  currentDate = new Date();
}
  1. DRY(Don’t Repeat Yourself)
    올려주신 코드를 보면 prevCalendarnextCalendar 함수의 로직의 정말 많은 부분들이 겹쳐 있습니다.
    이런 경우, 고차 함수 등을 이용해 추상화하시면 더 깔끔한 코드가 나옵니다.

  2. Functional 하지 않은 setState는 위험합니다.
    뒤에서 언급할 것이긴 합니다만… 작성하신 컴포넌트와 같이 setState가 한 Task에 여러 번 호출될 가능성이 있고, 수정되는 state의 프로퍼티가 겹치는 경우라면 setState의 로직이 분명 말썽을 일으킬 겁니다.
    functional setState is the future of react
    자세한 설명을 하게 되면 이야기가 길어지니, 관련된 글을 첨부하는 것으로 이 문제는 마무리하겠습니다.

  3. Ref을 남용하지 말아주세요.
    만약 Ref을 공부하기 위해 사용하시는 거라면 다른 좋은 예제를 만드시는 걸 추천해 드리고,
    그게 아니시라면 Ref을 잠시 치워두고 react스러운 방법을 찾아 고민해보시는 걸 추천해 드립니다.

Ref은 지금 사용하신 용도와 같은 용도로 사용하기 위해 만들어진 기술이 아닙니다.

자세한 건 아래 문서를 참조해 주시기 바랍니다.
Ref을 사용해야 할 때

  1. render 메서드는 pure 하게 유지해 주세요.
    올려주신 컴포넌트의 render 메서드는 prevCalendar 함수와 nextCalendar 함수를 매번 호출하고 있습니다. 기억하시나요? 저 두 함수는 컴포넌트의 상태를 바꾸는(impure 한) buildCalendar 메서드를 호출합니다.
    디버깅을 위해서, reactivity를 위해서, 코드의 평화를 위해서, render 메서드는 pure 하게 작성하세요.

react lifecycle diagram

  1. ‘react스러움’ 을 배우세요.
    코드가 전반적으로 react의 철학과 규칙을 무시한 체 작성되어 있습니다.
    react는 공식 문서들의 퀄리티도 뛰어난 편이고, 잘 정리되어 있으니 공식 문서에서 다시 한번 기초부터 살펴보시는 건 어떠신가요?

react


(서재원) #3

굳이 null 이 뜨는 이유를 알려드리자면, 제가 보여드린 문제점 중 9번째 문제점인 '순수하지 못한 render 메서드’가 원인입니다.

아직 아무것도 랜더링되지 않은 상태에서 buildCalendar 함수가 호출되니 ref들이 전부 null인 거지요.


(InGrowth) #4

코드를 붙이실땐 코드 버튼을 누르시거나, md 스타일로 작성해주세요.

// 요렇게요


(weaklion) #5

코드 어떻게 붙여쓰는지를 몰라서 그냥 붙여써버렸네요… 죄송합니다 .
다음부터는 // 붙이고 작성하겠습니다!
아직 저도 react가 많이 부족하네요… 여러분들 감사합니다!!


(InGrowth) #6

// 를 붙이는건 아니고요.

MD 스타일로 쓰시거나요. 글 쓸때 상단에 코드 표시를 눌러주시거나요ㅎㅎ